Contrast ratios: the WCAG numbers that decide if your text is readable
You ship a landing page, it looks great in your office, and then a customer emails: the gray text on your button is unreadable. Nothing is broken. Your gray on white passes on your monitor, in your lighting, with your eyes. It just fails for millions of people — and for Google, which now treats readability as a ranking signal through Core Web Vitals companion checks.
The WCAG numbers, in plain words
Contrast ratio runs from 1:1 (invisible) to 21:1 (black on white). Three thresholds matter:
- 4.5:1 — normal body text (AA, the legal baseline in most accessibility laws)
- 3:1 — large text (18pt+ or 14px bold) and UI elements like buttons and input borders
- 7:1 — AAA, the level you want for long-form reading
The trap everyone falls into: mid-gray. #888 on white is 3.5:1 — passes for large text, fails for paragraphs. #767676 is the lightest gray that passes 4.5:1 on white. Anything lighter in body text is a defect, not a style choice.
Check before you pick, not after you ship
The contrast checker takes your foreground and background colors and returns the exact ratio plus which WCAG levels pass. The workflow that saves rework: check your text colors inside your palette, because a pair that works on white often fails on the light-gray card you put behind it. Card backgrounds are where contrast quietly dies.
Color blindness is a design constraint, not an edge case
Roughly 1 in 12 men has some form of color vision deficiency. If your error state is "the field turns red" and your success state is "the field turns green," a deuteranope user sees both as muddy brown. The color blindness simulator renders your palette through the three common deficiency types in seconds — run it before finalizing a palette, not after a support ticket.
The fix is almost always the same: never encode meaning in color alone. Add an icon, a label, a border — anything that survives grayscale. Then color becomes decoration instead of information, and accessibility stops being a risk.
Where dark mode breaks everything
Dark mode inverts the math. Pure white text on pure black actually produces visual halos for astigmatic readers (a large share of the population), which is why mature dark themes use off-white on off-black, and why the pair you verified in light mode tells you nothing about dark mode. Verify each theme separately: every background/text pair, both modes, every state.
Run the numbers once per palette, keep the passing pairs in a small internal reference, and contrast stops being an argument in code review. It becomes a table everyone copies from.