About me

Selected work

Selected writing

Experience

Skills

Education

Archive

Accessibility is at least three settings, not one

Technical note · 2026

TL;DR: "accessibility settings" isn't one feature, it's at least three unrelated ones — text, color, and motion — and treating them as one menu is fine as long as you don't also treat them as one problem. I built the text and color halves of that menu at Nesta with Luca Bonavita; motion wasn't in scope because the products barely used it, and I only came back to that category years later, on a different site.

Public-sector work at Nesta came with real accessibility requirements, and a separate, more mundane problem: every product team was reinventing text-size and color-vision controls slightly differently. So the brief was “build one accessibility menu, themeable enough to standardize across Svelte products, and compatible with the way those products defined typography.” Luca and I worked on it together; the important part for this note is the design constraint we were trying to make reusable.

Text: one absolute number, everything else relative

The text-scaling requirement had a second, unstated constraint bolted onto it: it had to stay compatible with a glyph-metrics-based responsive layout system we’d built just before this — which measures rendered typography to decide breakpoints. So the rule the menu depends on isn’t just “use relative units so scaling works,” it’s “use relative units so scaling composes with a design system that’s already treating typography as layout data.”

Concretely: the font driver sets font-size on body as a rem value derived from the accessibility setting’s percentage (100% → 1rem), which scales relative to whatever base is set on html rather than writing one fixed pixel value straight onto the root. The component’s own README states the underlying convention plainly: use em/rem throughout, set the one absolute base on html, and adjust body’s relative value for accessibility. One number changes; everything downstream that’s expressed in em/rem follows it automatically, because that’s what relative units are for.

I like this rule enough that I applied a version of it to this site’s own CSS recently, unprompted by this article — a single html { font-size: 16px } with the rest of the stylesheet converted to rem, specifically so a future font-scaling control here wouldn’t need to touch more than one line. Writing about the Nesta version made me notice I hadn’t finished doing the same thing at home; now I have.

WAIfinder waits for the font loader first

A short, self-contained dependency worth naming on its own: a companion FontsLoader component uses the FontFace API to priority-load whichever typeface the accessibility menu currently has selected, then loads the rest of the font set behind it, exposing an isFirstLoaded flag. The reason it exists is entirely about ordering — you cannot sensibly measure glyph width with a font that hasn’t rendered yet. In WAIfinder, the root layout waits for that first font to load before mounting ScreenSensor; fonts load, then glyphs get measured, then layout decisions get made. The accessibility menu is the thing that can change which font is loaded first, which is why these pieces had to be composed in that order.

Color: what shipped, and what’s still just an idea

The color half of the menu does real work today: SVG filter matrices apply color-vision-deficiency modes for protanopia, deuteranopia, and tritanopia, alongside hue-shift, grayscale, invert, brightness, and contrast controls. All of that adjusts rendered color values before they reach the user. I would not oversell those matrices as a complete or authoritative correction system — they are filter-based adjustments, useful in practice, but still a fairly blunt instrument.

What it does not do, and what I still think about, is a genuinely different technique: automated hue-based texturing of images and data visualizations, so two regions that collapse to the same perceived color even after filtering can still be told apart by pattern rather than hue — a spatial fix for a problem color adjustment alone can’t solve, aimed specifically at charts and photographic imagery. The idea got onto my list through Derek Torsani’s Visually Accessible Data Visualization, a Plaid Design article that was especially useful because it came from a designer writing from color-blind experience, not just from an abstract accessibility checklist. Luca and I decided not to build that part under Nesta’s timeline, and it has stayed on my list since. If I revisit it, my own Color Lab project is the more likely place to prototype it than a Svelte component library. Worth being precise about that split — “already shipped, adjusts color” and “not built, would add texture” are two different claims, and it would be easy to blur them into one vague “accessibility for colorblind users” line that overstates what actually exists.

The gap: no motion setting at all

Here’s the one I’d rather state plainly than have someone else point out: this menu has a text group and a color group and nothing for motion — no prefers-reduced-motion support, no manual override, nothing. At the time, WAIFinder and the other Nesta products barely used motion at all, apart from details like smooth map zooming, so motion was treated much like the advanced color-texturing idea: a valid accessibility concern, but not something the time and money budget could justify building into the shared menu.

ferreyrapons.com — this site — later got an explicit motion toggle of its own, built independently and for a different reason (a WebGL-heavy home page that needed a real off switch). Looking at the two side by side is still a useful comparison: the more sophisticated font-scaling and color-correction system came first, and the simpler accessibility feature — “let people turn off motion” — showed up years later, on a project where motion had become a first-order interface concern. Good decomposition would have named motion as a separate category from day one, even if the right product decision at Nesta was still to leave it out.

Where the two Svizzle systems actually talk to each other

The coupling between this menu and the glyph-metrics sensor isn’t incidental cross-referencing between two unrelated notes — the menu reads $_screen.glyph.width directly to decide how certain settings render (a stepper control when there’s tight horizontal room, radio buttons when there’s space for them), and its own documentation states plainly that it depends on a ScreenSensor being mounted somewhere in the tree. They were designed as a pair, in the order that made physical sense: measure first, then let a settings menu react to what got measured, rather than pretending the menu’s own layout is exempt from the same width constraints it’s helping the rest of the app manage.

Decompose before you standardize

An “accessibility menu” isn’t one setting, or even one kind of setting — it’s several genuinely independent concerns (text, color, motion, and probably more depending on the product) that happen to share a UI panel. Building it as one relative-units system with one absolute anchor made the text half correct by construction; leaving motion out was reasonable for products that barely moved, but naming it as its own category would have made the scope decision explicit instead of implicit.

If you’re standardizing something similar across products and want the actual driver code, the SVG filter matrices, or a longer answer on the glyph-width coupling than fits here, ask.

— Seb · get in touchferreyrapons.com

← Selected writing Home Résumé