# Contrast ratio

## 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:

```js
// 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](/portfolio/closed-the-colour-system-at-28-colours-133/)
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](/portfolio/took-wcag-2-2-level-aa-across-the-whole-site-130/)
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](https://www.w3.org/TR/WCAG22/#dfn-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)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)
  — 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](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html)
  — the 3:1 rule for controls, their states and meaningful graphics, with pass and fail examples.
- [Understanding SC 1.4.6 Contrast (Enhanced)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html)
  — the AAA level of 7:1 and the readers it is meant for.
- [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/) — the sRGB transfer function and
  the colour spaces that the luminance formula assumes.

<https://engineer.company/glossary/contrast-ratio/>
