Accessibility Is a Design System Problem
Accessibility breaks at scale when it lives in individual screens. Here's how I bake contrast, type minimums, focus, touch targets and motion into tokens instead.
Most accessibility bugs I run into aren't made by people who don't care. They come from people who cared on one screen, in one sprint. Then the next designer duplicated the frame, swapped a color, and the care didn't come along for the ride.
That's the real problem. If accessibility lives in individual screens, it has to be re-earned every time someone builds a new one. If it lives in the design system, it ships by default. One of those scales. The other depends on everyone remembering everything, forever.
If accessibility has to be remembered, it will eventually be forgotten.
So here's how I think about moving accessibility out of the review checklist and into the tokens and components themselves.
Color ramps that know their own contrast
Most color ramps are built for looks first: nice even steps from light to dark, pleasant in a swatch grid. Contrast gets checked later, one pairing at a time, usually when something already looks off in QA.
Flip that. When I build a ramp, I want to know up front which steps pass against white and which pass against the darkest neutral. WCAG asks for 4.5:1 for body text, and 3:1 for large text and for UI parts like input borders and icons. If the ramp is documented with those ratios, a designer reaching for primary-500 on white knows immediately whether it can carry text or only decoration.
Then encode it in the semantic layer. Instead of letting people grab raw primitives, give them tokens like text-primary, text-on-accent and border-interactive that are already mapped to passing pairs. The primitive ramp can stay flexible. The semantic tokens are the promise.
- Note the contrast ratio of every step against your light and dark backgrounds.
- Only map passing pairs into text and icon tokens.
- Recheck the whole mapping when you add a dark theme. A step that works on white rarely works in the same role on near-black.
Give the type scale a floor
Type scales tend to grow downward. Someone needs a tiny caption for a chart, then a smaller legal line, then a badge label, and suddenly the scale has a 10px step that only exists because nobody said no.
WCAG doesn't set a minimum font size, which is exactly why the system should. I like to define a readable minimum as part of the scale itself, not as a guideline in a doc nobody opens. For most interfaces that means body text around 16px, secondary text that doesn't drop below 12px, and line heights generous enough that a paragraph doesn't turn into a wall. In code, using rem units lets the scale respect the user's browser font settings instead of fighting them.
If the smallest text style is 12px, nobody ships 9px text without detaching a style first. And detached styles are something you can actually catch.
Bake interaction states into components
In my experience, focus is the state most likely to be missing from a component spec. The default state gets polished, hover gets a nice tint, and focus is left to whatever the browser does. Or it gets removed with outline: none because the ring looked messy in a screenshot.
A focus ring should be a first-class token: a color with at least 3:1 contrast against the surfaces it sits on, a width, and an offset. Use :focus-visible so keyboard users get a clear ring and mouse users don't see it flash on every click. Once it lives in the button, input, card and menu item, every team gets it for free.
Touch targets
Same idea for hit areas. I treat 44 by 44 pixels as the minimum interactive area, which lines up with Apple's guidance and with WCAG's enhanced target size criterion. WCAG 2.2 sets a lower AA floor of 24 by 24, but if your components start at 44, you never have to think about the floor. The icon can stay 20px. The padding does the work.
Motion
Motion tokens should come in pairs: the normal duration and easing, and what happens when someone has prefers-reduced-motion turned on. Usually that means swapping big movement for a quick fade, not killing feedback entirely. If the tokens handle it, nobody has to remember to write that media query in every feature.
Documentation and linting are part of the system
A token nobody understands is just a hex code with a name. Every accessibility decision in the system deserves a sentence of "why" next to it: why this pairing is reserved for large text, why the icon button has extra padding, why the focus ring has an offset. People respect constraints they understand and route around the ones they don't.
Then automate the boring part. In code, that can mean a JSX accessibility lint rule catching missing labels, axe running in CI, and visual tests that include focus states. In design files, it means regular sweeps for hardcoded colors and detached text styles, because every one of those is a spot where the system's guarantees quietly stopped applying.
WCAG is the floor, not the finish line
WCAG is incredibly useful, but it's a minimum. Passing 4.5:1 doesn't mean text is comfortable to read on a phone in bright sunlight. A 24px target passes AA, but try hitting it while walking. Treating the guidelines as a checkbox gets you a compliance report. Treating them as a floor gets you a product more people can actually use.
This is also where accessibility stops being a niche feature in people's heads. Clear contrast, readable type, visible focus and calm motion help everyone: the person with a cracked screen, the person on a keyboard because their trackpad gave up, the person reading on a bumpy bus.
The shift I keep coming back to is simple. Stop asking every designer to be an accessibility expert on every screen. Ask the system to be the expert once, then make the right choice the easy choice. Screens come and go. Tokens stick around.