Guide
WCAG color contrast explained
By Haikal Akif · Updated · 6 min read
WCAG 2 measures contrast as a ratio of relative luminance from 1:1 to 21:1. Level AA asks for 4.5:1 for normal text and 3:1 for large text and UI components; Level AAA asks for 7:1 and 4.5:1.
The thresholds at a glance
| What | AA | AAA | Success criterion |
|---|---|---|---|
| Normal text | 4.5:1 | 7:1 | 1.4.3 / 1.4.6 |
| Large text | 3:1 | 4.5:1 | 1.4.3 / 1.4.6 |
| UI components and meaningful graphics | 3:1 | — | 1.4.11 Non-text Contrast |
| Logos, disabled controls, pure decoration | exempt | exempt | 1.4.3 |
Large text means at least 18 point (24 CSS px) at regular weight, or at least 14 point (about 18.66 CSS px) bold. Everything smaller is normal text. The numbers are the same in WCAG 2.0, 2.1 and 2.2.
How the ratio is calculated
Each color is converted to relative luminance (L): the sRGB channels are linearised to undo the screen’s gamma, then weighted by how sensitive the eye is to each one. Green dominates, blue barely counts.
// channel: 0–255 → linear light 0–1
const lin = (c) => {
const s = c / 255
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
}
const luminance = ({ r, g, b }) => 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
// lighter luminance on top
const contrast = (a, b) => {
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x)
return (hi + 0.05) / (lo + 0.05)
}White has L = 1 and black L = 0, so the maximum is (1 + 0.05) / (0 + 0.05) = 21:1. The grey #767676 on white scores 4.54:1, which is why it is the lightest grey commonly quoted as passing AA; #777777 scores 4.48:1 and fails. (Some WCAG texts print the linearisation threshold as 0.03928; at 8 bits per channel the result is the same.)
Hue does not count
The formula only sees luminance. A saturated red and a saturated green that look completely different can score close to 1:1, and many people with color vision deficiencies will see exactly that: two similar shades. This is why WCAG has a separate rule, 1.4.1 Use of Color, saying color must not be the only way information is conveyed.
Some common brand colors as text on white, measured with the formula above:
| Color | On white | AA normal text |
|---|---|---|
#2563EB blue | 5.17:1 | Pass |
#DC2626 red | 4.83:1 | Pass |
#7C3AED violet | 5.70:1 | Pass |
#16A34A green | 3.30:1 | Fail (large text only) |
#F59E0B amber | 2.15:1 | Fail |
Yellows, oranges and light greens almost never pass as text on white. Use them as backgrounds behind dark text instead: black on #F59E0B reaches 9.8:1.
Non-text contrast (1.4.11)
Since WCAG 2.1, the visual parts needed to identify a control and its state need 3:1 against adjacent colors. That covers input borders, checkbox outlines, toggle states, focus indicators and the parts of a chart that carry meaning. A pale grey border on a white input (#E5E7EB on white is about 1.2:1) does not identify the field on its own; a visible label or a filled background can make up for it.
What about APCA?
APCA (Accessible Perceptual Contrast Algorithm) is a newer model that accounts for font weight and size, and for the fact that light text on dark and dark text on light are perceived differently. It was proposed for a future WCAG 3, which is still a working draft. APCA is not part of WCAG 2.2 and is not a legal or conformance requirement. If you must conform to WCAG 2.x (for example under the European Accessibility Act or ADA-related policies), the 4.5:1 and 3:1 ratios are what you are measured against. APCA can be a useful second opinion, especially for dark mode, but it does not replace them.
Fixing a pair that fails
- Keep the hue and change lightness. Tinta’s contrast checker does this automatically: it keeps hue and chroma in OKLCH and moves lightness until the pair passes AA.
- Swap the roles. A color that fails as text often works as a background behind near-black text.
- Increase size or weight so the pair qualifies as large text (3:1), but only where the design really calls for large type.
- Use plain ink. The White and Near-black rows of the contrast matrix on every Tinta palette page show which one works on each color.
Every color page, for example #7C3AED, lists its contrast against white and black, and every palette in the library shows a full contrast matrix.