WCAG Contrast Ratios: What 4.5:1 Actually Means
"Your text needs a contrast ratio of at least 4.5:1." You've seen this in an audit, a Lighthouse report, or a design review. But what is that ratio measuring, where do the magic numbers come from, and why does the threshold change for big text? Here's the whole model.
Contrast is a ratio of luminance, not color
The WCAG contrast ratio compares the relative luminance of two colors — the foreground (text) and the background. Luminance is roughly "how much light the color emits," computed from its RGB channels with a perceptual weighting (green counts more than red, red more than blue, because that's how human vision works).
The ratio is defined as:
(L_lighter + 0.05) / (L_darker + 0.05)
where L is relative luminance from 0 (black) to 1 (white). That + 0.05 accounts for ambient screen glare. The result ranges from 1:1 (identical colors, invisible) to 21:1 (pure black on pure white).
A crucial consequence: contrast depends on lightness, not hue. Two vivid, very different colors — say a saturated red and a saturated green — can have terrible contrast if they're similarly light. This is why "it looks colorful and distinct to me" is not a substitute for measuring.
The thresholds
WCAG defines two conformance levels, and the bar depends on text size:
| Normal text | Large text | |
|---|---|---|
| AA (the common legal target) | 4.5:1 | 3:1 |
| AAA (enhanced) | 7:1 | 4.5:1 |
"Large text" means 18pt (≈24px) or larger, or 14pt (≈18.66px) bold or larger. The reasoning: bigger, heavier letterforms have more pixels and thicker strokes, so they stay legible at lower contrast. That's the entire basis for the relaxed 3:1 threshold.
Non-text elements — icons, input borders, focus indicators, the meaningful parts of a chart — have their own requirement: 3:1 against adjacent colors (WCAG 1.4.11). A button people can't find because its border is too faint fails accessibility just like unreadable text does.
Common ways people fail it
- Gray-on-gray placeholder text.
#999on white is only ~2.8:1 — it fails AA for normal text. Light gray "secondary" text is the single most common violation. - Brand colors used as text. A mid-tone brand blue or green often clears 3:1 (fine for a large heading or an icon) but misses 4.5:1 for body copy.
- Text over images or gradients. Contrast varies pixel by pixel; the worst spot is what counts. Add a scrim or text shadow.
- Disabled controls are exempt from the contrast requirement — but don't use "disabled" styling for text that's meant to be read.
Hitting the target
You usually fix contrast by adjusting lightness, not by abandoning your hue. Darken the text (or lighten the background) in small steps and re-measure — often a 10–15% lightness shift takes you from failing to passing without changing the color's character. Working in HSL makes this easy because you're moving one axis.
The color & contrast tool computes the exact ratio for any foreground/background pair and tells you which levels (AA, AAA, large-text) you pass, so you can nudge a value until it clears the bar. If you're adjusting lightness by hand, a HEX ↔ HSL converter lets you change the L channel directly and convert back.
Takeaways
- Contrast is a luminance ratio from 1:1 to 21:1 — driven by lightness, not hue.
- AA = 4.5:1 for normal text, 3:1 for large text; AAA = 7:1 / 4.5:1.
- "Large" = 24px regular or ~18.66px bold.
- UI components and focus indicators need 3:1.
- Fix failures by adjusting lightness and re-measuring — never eyeball it.
If you remember one number, remember 4.5:1 for body text. Everything else is a variation on it.