Why contrast is a number, not an opinion
Readability is not a matter of taste. Text you read effortlessly in a dim office can vanish for a person with low vision, for someone with cataracts, for the same user under direct sunlight or on a phone dimmed to save battery. That is why WCAG (the W3C Web Content Accessibility Guidelines) does not ask for pleasant colors, it defines the contrast ratio, a single reproducible value that every tool computes the same way, from the same constants.
The ratio compares the perceived brightness of two colors, text and background, and returns a number between 1:1 and 21:1. Identical colors give 1:1 (invisible); pure black on pure white gives the maximum, 21:1. Everything else sits in between, and the WCAG thresholds say where to draw the line. But note this up front: the number is honest about luminance and misleading about perception. It gets most cases right and a few predictably wrong, and the second half of this guide is about those errors.
The derivation: relative luminance and the ratio
The math has three stages. First, each sRGB channel (0–255) is normalized to 0–1 and linearized, you undo the transfer curve (the gamma correction) the screen applies, to get back to physical light. That curve has a knee: below a tiny threshold it is a straight line (divide by 12.92), above it a power with exponent 2.4. Then the three linear channels become a relative luminance, with very different per-channel weights because the eye sees green far more than blue. Finally, the ratio combines the two luminances, adding 0.05 to each.
lin(c) = c / 12.92 se c <= 0.03928
lin(c) = ((c + 0.055) / 1.055) ^ 2.4 caso contrario / otherwise
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
contraste = (L_claro + 0.05) / (L_escuro + 0.05)- c
- channel value (R, G or B) normalized to 0–1
- 0.03928
- the knee of the sRGB curve, verbatim from the normative WCAG text (strict sRGB now uses 0.04045; the gap vanishes at 8 bits)
- 2.4
- exponent that undoes sRGB gamma and returns linear light
- 0.2126 / 0.7152 / 0.0722
- luminance weights for R, G and B (green dominates; blue barely counts)
- 0.05
- assumed ambient reflection (screen flare): without it, black on black would divide by zero
Notice green: it weighs 0.7152, nearly three quarters of the brightness, while blue weighs only 0.0722. That is why vivid-blue text on a dark background often fails even when it looks vibrant: it contributes almost nothing to luminance. And because the ratio always puts the lighter color on top, the text/background order does not change the number, the contrast of A over B equals that of B over A. Hold on to that symmetry: it is convenient to compute and, as we will see, it is one of the formula’s defects. If the link between channels, hue and lightness still feels fuzzy, the color spaces: HEX, RGB and HSL guide shows how to move brightness without breaking the hue.
Two worked examples: the gray that fails by a hair
Let us derive one concrete color down to the final number. Take gray #767676 as text on a white #FFFFFF background, a tone that looks perfectly legible to the naked eye and that WCAG itself uses as a boundary example. Each channel is 0x76 = 118, so we normalize 118/255 = 0.46275, apply the linearization (the power branch, since 0.46275 > 0.03928) and reach the luminance. White has all three channels at 1.0, so its luminance is 1.0.
// Exemplo 1, #767676 (texto) sobre #FFFFFF (fundo)
// Example 1, #767676 (text) on #FFFFFF (background)
Canal / channel: 0x76 = 118 -> 118 / 255 = 0.46275
0.46275 > 0.03928 -> ((0.46275 + 0.055) / 1.055) ^ 2.4 = 0.18116
L(#767676) = 0.2126*0.18116 + 0.7152*0.18116 + 0.0722*0.18116
= (0.2126 + 0.7152 + 0.0722) * 0.18116 = 0.18116
L(#FFFFFF) = 1.00000
contraste = (1.00000 + 0.05) / (0.18116 + 0.05)
= 1.05 / 0.23116 = 4.5422 -> 4.54:1
// 4.54 >= 4.5 -> PASSA no AA de texto normal (por uma unha)
// 4.54 >= 4.5 -> PASSES AA normal text (by a hair)One step lighter, #777777, drops to 4.478:1, and here lives a subtlety that separates people who understand WCAG from people who memorize it. The norm says do not round: 4.478 is less than 4.5, so it fails, even though your head wants to round to 4.48. The checker goes further and truncates the display to 4.47, making it explicit that it sits below the floor. So #777777 fails normal text by hundredths, and darkening a single step to #767676 already clears the line. For a large heading, though, both pass comfortably, because the floor drops to 3:1.
| Text color | Ratio | Verdict |
|---|---|---|
| #777777 | 4.47:1 | Fails AA normal text (4.478 < 4.5); passes large text and UI. |
| #767676 | 4.54:1 | Passes AA normal text by 0.04; fails AAA. |
| #666666 | 5.74:1 | Passes AA; still fails AAA (< 7). |
| #595959 | 7.00:1 | Clears AAA, the lightest gray that still reaches 7:1 on white. |
View the data
| Category | Value |
|---|---|
| Blue #00F on black | 2.44:1 |
| Gray #949494 on white | 3.03:1 |
| Gray #777 on white | 4.47:1 |
| White on blue #0D6EFD | 4.5:1 |
| Gray #767676 on white | 4.54:1 |
| Gray #666 on white | 5.74:1 |
| Gray #595959 on white | 7:1 |
The chart makes the lesson visible: a saturated blue on black (2.44:1) fails everything despite looking intense, because blue barely produces luminance; and there is a narrow band, between 4.47 and 4.54, where two near-identical grays land on opposite sides of the 4.5 line. Now try to reproduce any bar: enter the two colors in the embedded checker below and read the ratio and the verdict for AA, AAA and components.
- Pick the two colorsEnter the text and background colors as HEX, RGB or a CSS name. Use the color converter if you need to translate between formats.
- Read the ratio and verdictCheck whether it passes at the level and size you use: AA normal (4.5), AA large (3), AAA (7) or UI (3).
- Adjust if it failsDarken (or lighten) the text until it clears the threshold; the tool suggests a nearby tone that passes 4.5:1, preserving the hue.
WCAG 2.2 thresholds and their history
Four WCAG success criteria involve color and contrast. 1.4.3 Contrast (Minimum) is Level AA and covers text (4.5:1 normal, 3:1 large); 1.4.6 Contrast (Enhanced) is the stricter AAA (7:1 normal, 4.5:1 large); 1.4.11 Non-text Contrast (AA) requires 3:1 for input borders, essential icons, focus states and graphical objects; and 1.4.1 Use of Color (Level A) forbids using color as the only means of conveying information. Large text gets a lower floor because bigger letters stay legible with less contrast.
| Criterion | Level | Target | Applies to |
|---|---|---|---|
| 1.4.1 Use of Color | A | Color must not be the only cue | Links, states, charts, forms |
| 1.4.3 Contrast (Minimum) | AA | 4.5:1 normal · 3:1 large | Text and images of text |
| 1.4.6 Contrast (Enhanced) | AAA | 7:1 normal · 4.5:1 large | Text and images of text |
| 1.4.11 Non-text Contrast | AA | 3:1 | UI components and graphical objects |
- What counts as large text
- Per WCAG, at least 18 point (with 1pt = 1.333px, that is ~24 CSS px) at normal weight, or 14 point bold (~18.5px). Below that, the normal-text thresholds apply. Since type scales fluidly today, see CSS clamp and fluid typography so you do not accidentally drop below the cutoff.
- Do not round the ratio
- WCAG is explicit: a ratio of 4.499:1 fails the 4.5:1 floor. That is why a gray like #777777 (4.478) fails, and why the checker truncates the display instead of rounding up.
- AA vs AAA
- AA is the practical target for most sites and most accessibility laws. AAA is the ideal for critical content, but it is not always achievable across an entire interface, the W3C itself does not recommend requiring AAA of a whole site.
- 1999WCAG 1.0
The first version measured color by brightness and hue difference, not by the luminance ratio, a method now abandoned.
- 2008WCAG 2.0 introduces the ratio
The recommendation defines relative luminance and criteria 1.4.3 (AA, 4.5:1) and 1.4.6 (AAA, 7:1). It is the formula we still use.
- 2018WCAG 2.1 and non-text
Adds 1.4.11 Non-text Contrast (3:1), extending the rule to borders, icons and focus states.
- 2023WCAG 2.2, and APCA out of the draft
WCAG 2.2 becomes a recommendation without changing the contrast formulas; the same year, APCA is removed from the WCAG 3 draft for further evaluation.
Where the formula fails, and what APCA is
The WCAG 2.x ratio is solid 2008 engineering with defects that are, today, well documented. It measures a luminance proportion, but human legibility is not a pure proportion: it depends on polarity, on font size and weight, and on the brightness the eye is adapted to. The formula models none of that continuously, so it errs in two directions, it passes hard-to-read combinations and fails some perfectly legible ones.
The second worked example shows the cleanest defect: polarity blindness. We saw that #767676 on white gives 4.54:1 and passes AA. Now flip it, white on a #767676 background. WCAG returns exactly the same 4.54:1, because the formula is symmetric: it puts the lighter color on top and ignores which is text and which is background. Yet, to the eye, dark text on a light background (positive polarity) usually reads better than light text on a mid gray at the same luminance difference. Two pairs, same number, different legibility, and WCAG 2 has no way to express it.
It passes the illegible: vivid red on black
Pure red #FF0000 on black gives 5.25:1 and passes AA for normal text. But saturated red on black vibrates and tires: the eye has low acuity for pure red and blue, and the missing supporting luminance makes edges shimmer. WCAG only sees the luminance proportion (red carries 0.2126 of it) and misses the perceptual cost of saturation. Fine on paper, rough on screen.
It fails the legible: the dark-mode hole
Because of the 0.05 and the shape of the curve, WCAG 2 compresses differences at the dark end. Light-text-on-dark pairs that a dark-adapted eye reads comfortably can get a low ratio and fail, while other dark pairs pass without earning it. The research behind APCA grew out of measuring exactly this discrepancy: in the dark, the luminance proportion stops predicting real reading.
APCA and WCAG 3: the real status
APCA (Accessible Perceptual Contrast Algorithm) is a perceptual approach: instead of a symmetric proportion, it produces a signed value (Lc) that changes with polarity and pairs with font size and weight tables. It was developed as a candidate for the contrast in the future WCAG 3 (the Silver project).
Be honest about the status: APCA is not normative in any W3C document today. In July 2023 the visual-contrast content (including APCA) was removed from the WCAG 3 draft for further evaluation, and the current draft states that WCAG 3’s contrast algorithm is yet to be determined. WCAG 3 remains a Working Draft in 2026, with a final recommendation projected no earlier than 2028. So APCA has not replaced WCAG 2, for conformance and for the law, this guide’s formula (4.5:1, 7:1, 3:1) is the one that counts.
Contrast does not solve color blindness
Passing contrast is necessary but not sufficient. About 8% of men and 0.5% of women of Northern European descent have some form of red-green color blindness, the National Eye Institute sums it up as roughly 1 in 12 men. The inheritance is X-linked: a man has a single X, so one faulty gene is enough; a woman has two, and the second usually compensates, which is why the prevalence collapses. For those people, two colors can have high contrast with each other and still be indistinguishable when the information depends on hue alone.
The classic case is a chart that separates gain and loss only by green and red, or a link that differs from body text only by being blue. That is why criterion 1.4.1 Use of Color (Level A) requires that color never be the only visual means of conveying information: add an underline to the link, an icon to the error message, a label to the chart slice, a pattern to the filled area. Test how your colors look under protanopia, deuteranopia and tritanopia in the color blindness simulator before locking the palette, and remember photos carry color too: the web image formats guide touches on profiles and gamma, which affect how color reaches the screen.
- Body text reaches 4.5:1 (AA); large headings at least 3:1, with no rounding up.
- Input borders, essential icons and focus state have ≥ 3:1 (1.4.11).
- No information relies on color alone (1.4.1): there is a supporting icon, text or pattern.
- The palette was checked in a color blindness simulator (protan, deuteran, tritan).
- Dark-mode pairs were tested with real eyes, not just the WCAG 2 ratio.
Frequently asked questions
What is the minimum contrast for text?
Does gray #777 pass on a white background?
Do icons and borders need contrast too?
Will APCA replace the WCAG 2 formula?
Does good contrast alone guarantee color accessibility?
Why does the formula add 0.05 to the luminances?
Treat contrast as a number, not an aesthetic: the ratio (1:1 to 21:1) comes from relative luminance, with 4.5:1 for normal text, 3:1 for large text and components, 7:1 at AAA, and no rounding up. But know the formula’s limits: it ignores polarity and treats size as steps, so it passes some illegible pairs and fails some good ones. APCA was born to fix this, but it is still a draft; WCAG 2.2 is what counts today. And color must never be the only cue, add an icon, text or pattern, and check the palette in a color blindness simulator.