What WCAG Contrast Ratios Do — and Miss
Understand WCAG contrast ratios, their limits, and practical checks for text, components, dark mode, and color-only cues.
WCAG contrast provides a repeatable minimum check for a rendered foreground/background pair. It is valuable precisely because it is consistent, but a ratio cannot decide whether a real interface has clear labels, usable states, readable text at zoom, or non-color cues.
Use the current WCAG formula for conformance work, then test the actual interface and user task.
The formula, in plain English
WCAG's contrast ratio is two numbers divided by each other. The numerator is the "relative luminance" of whichever of your two colors is lighter, plus 0.05. The denominator is the relative luminance of the darker color, plus 0.05. The output is always a number between 1 (same color) and 21 (pure white on pure black).
Relative luminance, though, is not what you might think. It isn't "how bright does this look." It's a specific calculation:
function luminance(r, g, b) {
const [R, G, B] = [r, g, b].map((c) => {
c /= 255;
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * R + 0.7152 * G + 0.0722 * B;
}
function contrast(rgb1, rgb2) {
const L1 = luminance(...rgb1);
const L2 = luminance(...rgb2);
const [lighter, darker] = L1 > L2 ? [L1, L2] : [L2, L1];
return (lighter + 0.05) / (darker + 0.05);
}
That weird piecewise bit in the middle — the c / 12.92 vs the Math.pow(...) — is a gamma correction. It's undoing the fact that sRGB, the color space your screen uses, is non-linear. The coefficients 0.2126, 0.7152, 0.0722 weight red, green, and blue by how much each contributes to perceived brightness: green matters most, red second, blue barely. This is why pure yellow on white has abysmal contrast even though yellow "looks bright" — yellow is red plus green, and both are already bright.
Here's the part nobody mentions: those luminance coefficients are from ITU-R BT.709, a broadcasting standard from 1970. They were chosen to match the phosphor primaries of consumer CRT televisions of that era. The formula has been tweaked once — the + 0.05 was added to the numerator and denominator to account for screen glare (your phone screen reflects some ambient light even at pure black, and 0.05 is a rough average). But it's fundamentally a formula designed for TV broadcasting in the Ford administration, applied to OLED phones under fluorescent office lighting.
This isn't a conspiracy — the WCAG working group knows this, which is part of why APCA exists. But if you've ever wondered why the math feels weirdly rigid, that's why. It's surprisingly old.
The four thresholds, and the exceptions nobody mentions
| Content | WCAG 2.2 AA | WCAG 2.2 AAA |
|---|---|---|
| Normal text | 4.5:1 | 7:1 |
| Large-scale text | 3:1 | 4.5:1 |
| Essential UI components and graphical objects | 3:1 | — |
“Large-scale” is about the rendered text size and weight — not whether an element happens to be an h1 or h2. Inactive controls and purely decorative content have specific exceptions, but an exception is not evidence that a control is understandable or easy to find. Visible placeholder text should be readable, and a form still needs a persistent label or equivalent instruction.
WCAG's relative-luminance definition uses the 0.04045 sRGB breakpoint. The older 0.03928 value appears in historic examples but is not the current formula.
The edge that isn't really an edge
Here's a thing to try. Open our contrast checker in another tab and punch in #767676 on #ffffff. The ratio is 4.54:1. Pass. Now try #777777 on #ffffff. The ratio is 4.48:1. Fail.
The colors are #767676 and #777777. One integer difference per channel. Nobody on earth can see the difference between those grays on a white background. But one passes WCAG AA and the other doesn't, and that difference is the whole thing separating "your product is accessible" from "you're getting a demand letter."
The reason is that the formula is continuous but the compliance bar is sharp. The ratio drops smoothly as you make the gray lighter; at some point it crosses 4.5, and the WCAG spec says "below this number, fail." Perceptually, the experience is gradual. At 4.5:1 the text looks slightly faded. At 4.0:1 it looks noticeably light. At 3.0:1 it's genuinely hard to read. But WCAG has no "slightly faded" bucket — everything that isn't 4.5 or greater is marked failing, and everything 4.5 or greater passes.
This is a known problem with the 2.1 formula and one of the things APCA is specifically designed to fix. For now, the practical workaround is: if you're near the edge, go darker. #595959 (contrast 7:1) is your safe "darkest light gray." Anything darker definitely passes AA and AAA, and you never have to argue with a checker again.
Six real debugging cases
1. A gray passes on white but fails on a surface
Test every foreground/background pair after compositing. A text color that passes on #fff can fail on a light card, overlay, or image.
2. A bright color is not necessarily a contrasting color
Yellow and pastel colors can be close to white in relative luminance. Change the foreground, background, or both; perceived brightness alone is not a ratio.
3. A link is only distinguished by color
Use an additional cue such as an underline, weight change, icon, or clear focus treatment. A foreground/background ratio cannot establish that people can distinguish a link from surrounding text.
4. Placeholder text is the only instruction
Put the instruction in a persistent label. Then make the placeholder a helpful example rather than the only way to understand the field.
5. A disabled control disappears into the page
An inactive-control exception does not make an invisible control usable. Preserve a discernible boundary, label, and explanation of how the action becomes available.
6. A brand pairing cannot pass
Do not force a failing foreground/background pairing into body copy. Preserve the brand with a different surface, a darker supporting token, or a non-text treatment, then test the actual pair.
For color-vision differences, test information conveyed by color with representative users and a dedicated simulator; contrast alone cannot establish color distinguishability.
Dark mode has its own failure mode
Everything above assumed light mode. Dark mode has different physics and different problems.
Pure white text (#ffffff) on pure black (#000000) has a 21:1 contrast ratio. The maximum possible. WCAG says this is the best you can do. Humans with low vision often describe reading that combination as painful. The phenomenon is called halation — on emissive displays like OLED, bright pixels on a dark background appear to "bleed" outward because of how the human eye resolves high-dynamic-range edges. Text gets fuzzy-looking. Extended reading causes eye strain.
What works better:
- Off-white text like
#e0e0e0or#d0d0d0, not pure white - Dark gray background like
#121212or#1a1a1a, not pure black - Contrast ratio of 12:1 to 16:1, not 21:1
This matches Google's Material Design and Apple's HIG recommendations for dark mode. It also, interestingly, still passes WCAG AAA easily — #e0e0e0 on #1a1a1a is 13.3:1. You can have better dark mode ergonomics and better WCAG scores by leaving pure black and pure white alone.
The other dark-mode gotcha: saturated colors (especially blues and purples) can become illegible on dark backgrounds even when the ratio passes. #5c6bc0 on #1a1a1a is 4.8:1 — passes AA. But the blue has so little luminance range from the dark background that the eye can't "lock onto" it. Desaturate or lighten the accent, not just ratio-check it. Rules alone won't catch this.
For dark-mode palettes, I generally build two parallel sets of the design system tokens and test both. The Color Palette Generator can help you generate a complementary-harmony palette that you then check in both modes.
APCA and current conformance work
APCA is a proposed contrast method, not a current substitute for WCAG conformance. WCAG 3 remains a Working Draft, so a product that needs to meet an existing requirement should use WCAG 2.2's applicable success criteria.
APCA can still be useful as an additional research or design signal, especially when exploring typography and dark-mode systems. Treat it as a complementary measurement, document the method and version used, and do not present an APCA result as a WCAG 2.2 pass or fail.
A debugging workflow that actually works
- Reproduce the reported pair with a reputable contrast checker.
- Inspect the rendered element in browser DevTools. Confirm the computed foreground, composited background, opacity, state, and font rendering.
- Decide whether to darken the foreground, lighten the background, or change both. Do not rely on a design-file swatch alone.
- Test focus, hover, selected, disabled, error, and dark-mode states separately.
- Test links and status messages with a non-color cue, keyboard focus, and a representative color-vision simulation.
- Record the approved foreground/background pair as a design token and retest it when a surface changes.
This workflow catches the common failure: a valid static pair that becomes invalid after opacity, an overlay, a state change, or a different surface token.
Frequently asked questions
Q: Why does #777 gray fail on white but #767 pass?
Because the luminance formula is non-linear near the top of the brightness range. #767676 has luminance 0.2086; #777777 has luminance 0.2105. That 0.002 difference flips the contrast ratio from 4.54 (pass) to 4.48 (fail). You can't see it; the formula cares. The honest fix is to go darker than the edge — #595959 or darker passes comfortably.
Q: What's the difference between AA and AAA? AA is the practical bar most audits use: 4.5:1 for normal text, 3:1 for large text. AAA is the enhanced level: 7:1 for normal, 4.5:1 for large. AAA is aspirational — most of the web doesn't hit it, and WCAG 2.1 explicitly says "achieving AAA for all content is not possible for some content types." For most commercial products AA is the target; AAA is for critical content (medical info, legal disclosures, government services).
Q: Is 3:1 enough for large text? What counts as large? Large text means 18 point or larger, or 14 point bold or larger. At 96 DPI, 18 points is 24 CSS pixels; 14 points is about 18.7 CSS pixels. Heading tags do not determine large-scale text. Check the rendered CSS size and weight; body text rarely qualifies. If in doubt, measure the rendered size — not the design file font size.
Q: Do icons and UI components need to pass contrast? WCAG 2.1 SC 1.4.11 says non-text content (icons, UI components, graphical information) needs 3:1 against adjacent colors. This includes active states of form controls, focus indicators, checkbox states, and meaningful graphics. It does not include decorative icons or UI elements inactive states.
Q: Does placeholder text need to pass contrast? Technically the 2.1 spec is ambiguous. In practice: if placeholder is the only thing telling the user what to enter, it needs 4.5:1. If there's a persistent label, 3:1 is defensible. Most teams just make placeholder hit 4.5:1 and move on, which is the safest interpretation and matches current legal trend.
Q: How do I pick a passing gray?
Bookmark #595959 — that's the lightest gray that passes 7:1 (AAA) on pure white. If you need a passing gray on white, and you start lighter than #595959, you're gambling. #767676 passes AA on white but only barely, and fails on any off-white.
Q: Why does yellow fail on white? Luminance math weights green heavily and red moderately. Yellow = red + green, both at full, so it's almost as luminous as white. Pure yellow on pure white is about 1.07:1. Yellow on a dark background (especially black) scores around 20:1 and works great. If you must use yellow with white, darken toward amber.
Q: What is APCA and when should I switch? APCA is a proposed replacement for the WCAG 2.1 contrast formula, more accurate to human perception, polarity-aware (dark mode works differently than light mode), and intended for WCAG 3. It's not normative yet. Right now, design to WCAG 2.2 as the floor and use APCA as a secondary sanity check. Products that care deeply about dark-mode ergonomics or serve low-vision users might want to adopt it earlier via bridge-pca.
Q: Is dark mode harder to make accessible?
Not harder, but different. Pure white on pure black scores 21:1 but causes halation. Off-white on dark gray (e.g., #e0e0e0 on #1a1a1a, 13.3:1) reads more comfortably. The contrast formula doesn't know about halation. Also saturated accent colors (blues, purples) can become hard to read on dark even when they ratio-pass. Test in both modes with actual users.
Q: Do hover states need contrast? The hover state itself doesn't need to pass (it's not the default state), but any information conveyed by the hover state — like "this button is now focused" — does need to be visible to keyboard users. Focus indicators specifically must meet 3:1 against the component's default state per WCAG 2.1 SC 1.4.11.
Q: Why do two colors that look different have the same contrast ratio? Contrast ratio is a 1-dimensional output (luminance ratio) of a 3-dimensional input (R, G, B). You can have many different color pairs with the same luminance ratio. This is another case where the formula simplifies reality — two colors can have identical ratio and very different perceived readability.
Q: How do I test contrast in Figma, Chrome DevTools, or my existing design? Figma plugins: Stark (free tier), Able, Contrast. Chrome DevTools has built-in contrast check in the element panel (click any text → go to Styles → look for the contrast icon next to the color). Or use a quick online checker like ours with copy-paste. For batch testing whole pages, axe DevTools or WAVE will flag all contrast failures at once.
Q: Can text on images pass WCAG? Only if the image is decorative (WCAG gives an explicit exemption for text over decorative images). If the text is meaningful (button labels, hero headings), the WCAG requirement is that the text has sufficient contrast against whatever is behind it — and since an image varies pixel by pixel, this is impossible to guarantee without either (a) a semi-transparent overlay that brings the background luminance under control or (b) a text shadow that creates an artificial high-contrast outline. Both are common patterns; both are acceptable.
Q: What happens if my site fails WCAG — can I get sued?
In the US, yes. ADA lawsuits citing WCAG 2.1 AA as the standard have been filed against thousands of companies in the past five years. A demand letter typically cites specific failures (e.g., "body text #999 on #fff, ratio 2.85:1, fails AA"). Settlement or remediation is usually cheaper than litigation. Outside the US, the regulatory environment varies — EU is moving toward mandatory WCAG 2.1 AA compliance for public sector sites via the European Accessibility Act (effective 2025).
One more thing
Contrast is a minimum measurement, not a complete reading-experience score. Use it alongside clear labels, focus visibility, non-color cues, zoom testing, real content, and feedback from people who rely on those accommodations. The goal is not a certificate; it is usable information.
Practice with free tools
Practical browser tools with data-handling disclosures and reviewed limits on flagship workbenches.
Browse all tools →