Color contrast is the easiest accessibility win. Here's how to not mess it up.

Contrast gets fixed more often than it gets solved. Here's the difference.

Color contrast comes up first in almost every accessibility audit, and for good reason: it's measurable, it has clear criteria, and failing it affects more people than almost any other single issue. A user with low vision who can't read your body text is having a broken experience, not a degraded one.

It also gets "fixed" more often than it gets solved. Teams run a contrast checker, update the values that fail, check the box, and ship. Often without understanding what the checker is actually measuring or what they got right by accident.

What the numbers mean

WCAG 2.x defines contrast at two levels: AA (minimum compliance) and AAA (enhanced). For normal text under 18pt regular or 14pt bold, AA requires a 4.5:1 ratio against the background. Large text gets a break at 3:1. Non-text elements like icons and input borders: also 3:1.

These numbers come from a luminance formula modeled on human vision, adjusted for conditions typical to moderate low vision. 4.5:1 is roughly the threshold where text becomes legible for someone with 20/80 vision without corrective aids. Not arbitrary.

The thing teams often miss: the ratio is calculated against the actual rendered background. If your body text sits on #ffffff in light mode, that's your background. If it sits on a surface-tinted card at #f1f5f6, that's a different calculation. Tools that only check against white will miss failures on tinted surfaces.

Where it keeps going wrong

Placeholder text is the most consistent failure I see. The value that feels "subtly appropriate" in an input field is usually around 3:1 at best. WCAG includes placeholder text in the normal text requirement. If your placeholder looks tastefully light, it probably fails AA.

Disabled states are a real edge case. WCAG explicitly exempts inactive components from contrast requirements. A disabled button at low contrast is conformant. But "disabled" has a specific meaning: the component won't become interactive based on user action. If you're using low-contrast styling to indicate something temporarily unavailable, like a submit button before the form is complete, that's different. Getting it wrong hides functionality from users who already have a harder time seeing it.

Focus indicators are where custom styling creates systematic problems. WCAG 2.1 AA has no numeric requirement for them, but 2.2 introduces 3:1 for focus visibility. Browser default focus rings often fail on colored backgrounds, and the custom styles that replace them are almost always made "less intrusive." Which usually means they blend into the surface. Focus visibility is not decoration.

Text over images is what no checker handles well. The background isn't a single value. It's a distribution across pixels. Checking the average luminance produces a number that means nothing. Add a semi-transparent overlay or scrim with enough opacity that text reliably passes, then test with actual images, not a color picker.

4.5:1 is the floor

A ratio of 4.5:1 is the minimum. Text sitting at exactly 4.5:1 is legible for many people with low vision. It's also uncomfortable for extended reading at normal vision in conditions that aren't ideal. Direct sunlight, a cheap display, late in the day when your eyes are tired.

If the only experience you're optimizing for is a controlled environment with a good monitor, you're not solving for the people these numbers were written for.

Body text people will read at length should be at the high end of the range, 7:1 or above. Full black on white is not heavy-handed. It's appropriate. Save the lower-contrast values for captions and metadata that users scan, not read.

Checking your work

Automated tools catch a meaningful portion of failures. They miss text over images, text where the background color is set programmatically, and interactive states where color changes on hover. Your tool is checking a snapshot.

Manual checking takes about 10 minutes per component. Browser devtools, Firefox Accessibility Inspector, and the Figma A11y plugin all work reasonably well. Run them on the rendered component, not the design file. Rendered colors diverge from Figma values more than most people expect.

The more reliable fix: build the checks into component specs. Document the required contrast for each text element in each state and assign someone to verify before the component ships. QA that isn't assigned to anyone doesn't happen.

Greg Sargent
Greg SargentDirector of Design Systems, Spring Health

I write about design systems, accessibility, and the way AI is changing how we build software.

Published October 22, 2025

Be the first to rate this article.