Skip to content
Craft & CSS

Colour contrast you can compute

Relative luminance is a formula, not a vibe. The maths that turns 'does this pass AA?' into a number you can compute on a napkin — and the three contrast facts that explain most design-review arguments.

Answers How do I check if my colour contrast is good enough?the maths is four lines, and the banner's four tones all clear 4.5:1 on the page background.

Intermediate2 min read (computed · recorded 10)updated 2026-09-10colourcontrastaccessibilitymath

Contrast ratio is a ratio of luminances

The WCAG contrast ratio is (L1 + 0.05) / (L2 + 0.05) where L1 is the lighter colour's relative luminance and L2 the darker's — nothing more. Relative luminance is where the maths lives: each RGB channel is linearized (divide by 255, then the sRGB curve) and weighted 0.2126 red, 0.7152 green, 0.0722 blue. Green dominates because the eye is most sensitive there — which is why two colours that 'look' equally bright on your monitor can fail contrast while a surprising pair passes.

luminance.ts
function channel(c: number) {           // 0..255 -> linear
  const s = c / 255;
  return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
}
function luminance(r: number, g: number, b: number) {
  return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
}
function ratio(a: number, b: number) {
  const [hi, lo] = a > b ? [a, b] : [b, a];
  return (hi + 0.05) / (lo + 0.05);   // >= 4.5 passes AA text
}

Three facts that settle arguments

  • White on near-black is ~15.8:1 — the ceiling you are always aiming under, and the reason pure black text on pure white (21:1) is not 'more accessible' than a well-chosen dark grey; it is just harsher.
  • Grey text on white fails long before it looks faint: #999 on white is 2.8:1 (fails AA for everything); #767676 is 4.54:1 (passes). The 'muted' text in most products is failing silently — the design review argument is really a luminance argument.
  • Brand colours almost never pass for text: most logo blues and reds land between 3:1 and 4.4:1 on white — fine for large type and UI components (3:1 AA), failing for body copy. The professional move is a text-ink variant of the brand colour, tuned darker, used for words.

The napkin workflow

When a palette arrives, compute three ratios before anything else: body text on background (needs 4.5), large text and UI icons on background (needs 3), and the brand accent as text on background (needs 4.5 or a darker variant). Failures get fixed in the token layer — a --text-muted token, a --brand-ink token — so the design system encodes the maths instead of re-arguing it per screen.

Practise the lesson

Theory sticks when you ship it. These original Motif assets put this guide's lesson to work — open one and copy it into your own page.