Contrast ratio
how far apart two colours are, as a number
What it is
A contrast ratio is the single figure, running from 1:1 to 21:1, that WCAG uses to judge whether text, icons and the other visible parts of a page stand out from whatever lies behind them. It compares the luminance of two colours, so it measures a difference in lightness and ignores hue entirely. Designers often say colour contrast or luminance contrast, and audits list contrast ratios per colour pair; all of these name the same calculation.
The formula is short. Take the relative luminance of the lighter colour, L1, and of the darker one, L2, and divide (L1 + 0.05) by (L2 + 0.05). Relative luminance is normalised to 0 for the darkest black and 1 for the lightest white. For an sRGB colour, each channel is first converted from its gamma-encoded value to linear light, and the three are then weighted: 0.2126 for red, 0.7152 for green and 0.0722 for blue. Two identical colours give 1:1, and black on white gives 21:1.
Those weights are where intuition breaks. Pure green on black measures about 15.3:1, while pure blue
on black manages about 2.4:1, although both look equally vivid. Pure red on white comes out at
3.998:1. Mid-grey #767676 on white measures 4.54:1, and #777777, one step lighter, measures
4.48:1. The arithmetic fits in a dozen lines:
// Contrast ratio of two sRGB colours, as WCAG 2.2 defines it
const linear = (c) => {
const s = c / 255;
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
};
const luminance = (rgb) => {
const [r, g, b] = rgb.map(linear);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const contrast = (a, b) => {
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (hi + 0.05) / (lo + 0.05);
};
contrast([118, 118, 118], [255, 255, 255]); // 4.54...
In WCAG 2.2 the ratio is a definition, and four success criteria set thresholds on it:
- 1.4.3 Contrast (Minimum), Level AA: at least 4.5:1 for text and images of text, and at least 3:1 for large-scale text. Text in an inactive control, pure decoration and logotypes carry no contrast requirement.
- 1.4.6 Contrast (Enhanced), Level AAA: at least 7:1, and 4.5:1 for large-scale text.
- 1.4.11 Non-text Contrast, Level AA: at least 3:1 against adjacent colours for whatever identifies a control and its state, and for the parts of a graphic needed to understand it.
- 2.4.13 Focus Appearance, Level AAA: the focus indicator changes by at least 3:1 between the focused and unfocused states across an area at least as large as the control’s perimeter drawn 2 CSS pixels thick.
Large-scale text is text of 18 point or more, or of 14 point or more in bold. In CSS pixels that is 24px, or about 18.7px when bold.
Why it matters
The thresholds come from measured vision loss, and knowing their origin helps when someone wants to bend them. WCAG’s Understanding document starts from 3:1, the minimum an ANSI standard set for observers with typical sight. Vision of 20/40 comes with a loss in contrast sensitivity of roughly 1.5, so 3 × 1.5 gives 4.5:1, and the same document notes that 20/40 is commonly reported as typical acuity at about age 80. The AAA figure of 7:1 compensates for vision around 20/80. Because the ratio uses luminance alone, it holds up across most colour-vision deficiencies, which hue-based rules would not. The same margin helps anyone reading a phone in bright sunlight.
So the decision a contrast ratio settles is which colours a brand may use for which job, and that decision is far cheaper at the palette than at the page. A brand colour that measures 3.2:1 on white still has work: large headings, icons and the outline of a form field, which need 3:1, backgrounds behind dark text, decoration, the logo itself. What it may not carry is body copy, link text in a paragraph or placeholder text, which all need 4.5:1. Deciding that once, in the design tokens, costs an afternoon. Finding it after launch means recolouring every component that borrowed the colour, and a dark theme doubles the number of pairs to check.
Contrast is also one of the few WCAG requirements a machine can verify exactly, provided it knows both colours. That makes it the first finding in most automated audits and the first question on many procurement checklists, and Level AA is the level that accessibility rules and contracts most often name. Reaching for AAA’s 7:1 everywhere is a choice to weigh: WCAG itself advises against requiring AAA as a policy for entire sites, but 7:1 is a sound target for long reading text where the palette allows it.
What it looks like in practice
Two pages on this site show the ratio kept as a recorded number rather than a judgement made by eye.
When the site’s colour system was closed, each of its 28 colours was documented with the contrast ratio it measures on the surface it appears on, and eleven brand values were declared once as tokens. A linter fails in five ways, and one of them is a colour recorded with the wrong ratio. The standing rule includes never lowering a documented ratio to make a design work, and a case for a twenty-ninth colour has to include its ratio. The same page sets a second threshold, a perceptual distance of 0.02 in a uniform colour space, which decides when two spellings are really one colour. That number tells palette entries apart; it is a different measure from contrast.
The harder case was text over a photograph. The page on taking WCAG 2.2 Level AA across the site records that an automated rule engine reports such contrast as unevaluable, and that the site had been defending it with an argument rather than a number. Measured, 49 of 64 page and theme pairs were below AA, and two surfaces the design had never owned turned out to be the cause. The check that found them is now a floor: every pair must carry AA over the wall the build draws, and a pair that falls short fails the build.
Where it goes wrong
Text over a photograph passes in the tool and fails on screen. WCAG measures text against its specified background and assumes white when none is set. A headline on a hero image has a declared background that is not the one under its letters, so a checker either measures the wrong pair or hands the element back for manual review. Measure against the lightest and darkest pixels the text can sit on at every crop and width, or give the text a solid or scrimmed backing whose colour is known.
A token measures 4.6:1 and the page shows 3.9:1. Opacity, rgba() text, a translucent overlay
or a blend mode changes the colour that reaches the screen, so the ratio recorded for the token
describes a colour nobody sees. Compute the ratio of the composited pair, or keep alpha off text
altogether.
An audit reports 4.49:1 as a pass. A ratio shown to one decimal place turns 4.49 into 4.5. The Understanding document is explicit that the thresholds are not rounded: 4.499:1 does not meet 4.5:1. Store at least two decimals and compare the raw value with the threshold.
The light theme is checked and the dark theme ships on trust. Every theme, and every state a colour can take, is its own pair: hover, focus, visited, error, and placeholder text, which counts as text and needs 4.5:1. A disabled control is exempt. Generate the list of pairs from the tokens, per theme and per state, and check the list rather than a screenshot.
Thin type passes on paper and fades on screen. The ratio comes from the specified colours, but anti-aliasing draws hairline weights fainter than their CSS colour, and the Understanding document names this case: such text can pass nominally and still read with much lower contrast. Raise the ratio for light weights, or choose a heavier cut of the typeface.
Further reading
- WCAG 2.2: contrast ratio — the normative definition, with its notes on background colour, white as the default and anti-aliasing.
- Understanding SC 1.4.3 Contrast (Minimum) — where 4.5:1 and 3:1 come from, how large-scale text is sized, and why ratios stay unrounded.
- Understanding SC 1.4.11 Non-text Contrast — the 3:1 rule for controls, their states and meaningful graphics, with pass and fail examples.
- Understanding SC 1.4.6 Contrast (Enhanced) — the AAA level of 7:1 and the readers it is meant for.
- CSS Color Module Level 4 — the sRGB transfer function and the colour spaces that the luminance formula assumes.