WCAG Contrast Ratio: How Text Readability Is Measured
The WCAG contrast ratio compares text and background luminance from 1:1 to 21:1 using the formula (L1 + 0.05) / (L2 + 0.05). Black on white reaches the maximum, 21:1; a colour on itself is 1:1.
Why it matters
Low contrast is the single most common accessibility failure on the web, and it is entirely measurable. That is why WCAG anchors readability to one number. It is not a matter of taste: #777 on white measures 4.52:1 and passes the 4.5:1 bar for body text, whilst #767 measures 4.48:1 and fails by 0.04 points. Pixel-perfect measurements, but the maths is sound.
People with low vision, presbyopia, or astigmatism struggle with low contrast far more than sighted readers. A ratio of 4.5:1 is not arbitrary; it is based on research into what low-vision readers can actually perceive. Meeting it takes minutes and costs nothing.
How it works
The contrast ratio is calculated from the relative luminance (perceived brightness) of the text and background colours:
Contrast ratio = (L1 + 0.05) / (L2 + 0.05)
Where L1 is the luminance of the lighter colour and L2 is the luminance of the darker colour. The 0.05 constant is there to avoid division by zero when comparing near-black values.
Luminance is not the RGB average. It is computed by linearising the sRGB channels and weighting them 0.2126R + 0.7152G + 0.0722B (more on that in the relative-luminance page).
The ratio ranges from 1:1 (identical colours, unreadable) to 21:1 (black on white, the maximum). WCAG sets the bar at 4.5:1 for normal text at AA level.
What does not matter
The ratio is symmetric. Swapping text and background does not change it. #777 text on white and white text on #777 have the same ratio, so the formula is not biased toward dark-on-light.
Also, the contrast ratio does not account for font weight or size directly. A bold 28px heading can pass at 3:1 (large text AA threshold), while the same colour at 12px and regular weight would fail. Context matters, and the spec lets you use larger text as a workaround.
Do not assume bigger is always better either. Pure black on pure white (21:1) can cause halation and eye strain for some readers; the useful range has a top as well as a bottom. Aim for 4.5:1 to 15:1 and you will be fine.
Code example
Calculating the contrast ratio for #38bdf8 (light cyan) on white:
// Hex #38bdf8
// Linearised luminance ≈ 0.72
// White (#ffffff)
// Linearised luminance ≈ 1.0
// Contrast ratio
const ratio = (1.0 + 0.05) / (0.72 + 0.05);
console.log(ratio); // 1.38:1
// This fails AA (needs 4.5:1 for normal text).
// Try a darker shade: #0891b2 (darker cyan)
// Luminance ≈ 0.30
// Ratio: (1.0 + 0.05) / (0.30 + 0.05) = 1.05 / 0.35 = 3.0:1
// Still fails. Go darker: #06b6d4
// Luminance ≈ 0.51
// Ratio: 1.05 / 0.56 ≈ 1.875:1
// Still below 4.5. For light cyan on white, you need
// something around #0891b2 (luminance ~0.30), which gives
// (1.0 + 0.05) / (0.30 + 0.05) = 3.0:1, still short.
// Light cyan simply cannot reach 4.5:1 on white.
// Use it on a darker background instead.
This is why colour selection matters: not every colour works on every background. A light colour works on dark; a dark colour works on light. The grid is what catches mismatches.
How Scalpel Color shows it
The Contrast tab lets you pick two colours (text and background) and shows the exact ratio, plus AA and AAA verdicts. Hover over a swatch in the Palette tab's contrast matrix to see the ratio for every pair. The extension calculates luminance transparently; you do not need to do the maths.
Related glossary
See wcag-conformance-levels for what AA and AAA ratios you need, relative-luminance for how luminance is calculated, and color-contrast-matrix for how to check every colour pair in your palette at once.