The contrast ratio formula
WCAG contrast ratio is calculated as (L1 + 0.05) / (L2 + 0.05), where L1 is the relative luminance of the lighter color and L2 is the relative luminance of the darker color. Luminance is calculated from the RGB values using a gamma-correction formula that approximates how the human eye perceives light.
The result is a ratio. Black on white = 21:1 (maximum). White on white = 1:1 (no contrast). The WCAG standards are:
| Level | Normal text (<18pt) | Large text (≥18pt or 14pt bold) | UI components |
|---|---|---|---|
| WCAG AA | 4.5:1 | 3:1 | 3:1 |
| WCAG AAA | 7:1 | 4.5:1 | No requirement |
Note: WCAG AAA is rarely required by law. Most accessibility mandates (ADA, Section 508, EN 301 549 in the EU) reference WCAG 2.1 AA. AAA is a voluntary enhancement.
Where 4.5:1 fails in real conditions
4.5:1 was derived from studies of people with moderately low vision — specifically, people with roughly 20/80 vision using standard displays at standard conditions. In practice, these conditions are rarely standard:
- Screen glare.Ambient light reflecting off a laptop screen can drop effective contrast dramatically. A 4.5:1 ratio on a non-glossy display in a controlled lab can feel like 2:1 on a glossy screen in a coffee shop by a window. WCAG doesn't account for ambient lighting.
- Aging eyes. Contrast sensitivity decreases with age. A 60-year-old requires roughly 2× the contrast of a 20-year-old to perceive the same level of clarity. 4.5:1 is calibrated for impaired young users, not typical older users.
- Low-brightness phone screens. Phone batteries last longer at low brightness. Many users browse with screens at 30–50% brightness, which compresses the effective contrast range.
- AMOLED display characteristics. OLED screens can display true black (0 nits) while IPS displays have a minimum brightness floor. The same CSS colors display at different effective contrast ratios on these two panel types.
The grey text problem
The most common accessibility failure I see in design reviews is grey body text on white backgrounds. Grey text is visually fashionable — it creates a hierarchy between headings and body — but it frequently fails at small sizes.
Specific values I've checked:
#6B7280(Tailwind gray-500) on white: 4.63:1 — barely AA compliant, fails in adverse conditions.#9CA3AF(Tailwind gray-400) on white: 2.85:1 — fails AA for normal text. Passes for large decorative text only.#374151(Tailwind gray-700) on white: 10.65:1 — solid, close to AAA. This is what I use for body text.#6B7280(gray-500) on#F9FAFB(gray-50): 4.17:1 — fails AA. A common pattern that looks fine in Figma but fails in production.
The lesson: don't check your text color against pure white if your actual background is light grey. Check against the actual background color you're shipping.
The large text exception is misused
The 3:1 ratio for "large text" (18pt or 14pt bold, approximately 24px regular or 18px bold in CSS) is meant for headings that are genuinely large — paragraph titles, section headers, page titles. I regularly see it applied to any text that's slightly bigger than the smallest font on the page, which is not the intent.
14px bold hero text in a light grey color, justified as "large text," and then checked against 3:1 — I've seen this in multiple design systems. If the text is below 18px (24pt) regular weight, it needs 4.5:1 regardless of whether it's bold.
UI components at 3:1
WCAG 1.4.11 (Non-Text Contrast, Level AA) requires a 3:1 ratio for UI components like buttons, form inputs, and icons. This applies to the component boundary against its background — for example, the border of an input field against the page background.
Common failure: a white button on a light background with only a 1px border in#E5E7EB (gray-200). The border-to-background contrast is around 1.5:1. The button is visually present for most users but fails WCAG 1.4.11. Fix: darken the border to #9CA3AF (gray-400) at minimum, which gets you to 2.85:1 — still technically a fail, so push to #6B7280 (gray-500) for 4.17:1 to leave a margin.
APCA: the upcoming replacement for the WCAG contrast formula
The WCAG contrast ratio formula has known issues — it's based on color science from the 1980s and doesn't accurately model how modern displays render color or how the human visual system processes contrast for text. The W3C is developing WCAG 3.0 with a new algorithm called APCA (Advanced Perceptual Contrast Algorithm) that accounts for font weight, font size, and display polarity (dark-on-light vs light-on-dark) separately.
APCA isn't finalized or legally mandated yet, but it's worth knowing it exists. For now, WCAG 2.1 AA is the standard to comply with, and WCAG 3.0/APCA is where the conversation is heading.
How I test contrast in practice
Three checks I do before shipping any new color combination:
- Check the exact foreground/background hex values using the contrast checker. Not an approximation — the actual CSS values that will ship.
- View the design at 50% screen brightness on a laptop, in a room with a window behind me (to simulate glare). If it's still easy to read, I'm satisfied.
- Check on a phone. Phones render fonts differently than desktops, and the smaller screen size means text often renders at effectively smaller sizes.
If the color passes WCAG AA and all three of the above, I ship it. If it only barely passes AA (under 5:1) and is used for body text, I push the foreground darker until I hit at least 6:1, which gives me a buffer for real-world conditions.
Related tools
- Color Contrast Checker — check any foreground/background pair against WCAG AA and AAA instantly.
- Color Picker — pick colors and copy them in hex, RGB, or HSL format.
Written by Achraf A., founder of TheFreeAITools — built in Morocco. Contrast ratios cited were measured using the tool on this site and verified against the WebAIM contrast checker.