technique

contrast

Computing the W3C WCAG text-contrast ratio from two HSLuv lightness values by converting each to relative luminance and applying (l1+0.05)/(l2+0.05).

Also called: WCAG contrast ratio · relative luminance contrast ratio · chroma.contrast · contrast ratio

Run it

What the contrast ratio actually measures relative luminance · why hue does not appear in the formula · the 4.5:1 line
What it looks like when it goes wrong (4)
  • Red against cyan — about as different as two colours get — and the ratio sits near 1.1:1, far under the 4.5:1 needed for body text. Hue does not appear anywhere in the formula. This is the single most common accessibility surprise in generative work: a palette picked for the plotter looks vivid and is unreadable, and no amount of choosing more different hues will fix it.
  • Pure blue on black scrapes past 3:1. Set hue A to 120 for green at the same lightness and it jumps past 15:1. The luminance weights are 0.2126 R, 0.7152 G, 0.0722 B — green carries nearly three quarters of perceived luminance and blue barely registers. Blue text on a dark ground is the classic instance.
  • Read the number, then swap the lightness values and read it again — identical. The wanted spec expected the ratio to invert, because the WCAG formula is written as (L1+0.05)/(L2+0.05) with L1 "the lighter". It is defined lighter-over-darker, so it is symmetric by construction and a swap toggle would have done nothing. Worth knowing before you build a checker around it and puzzle over why the button has no effect.
  • Mid grey on white lands just under 4.5:1 and the verdict flips to large-text-only. One step of lightness moves it back. Treating the threshold as a hard boundary between legible and not is how palettes end up tuned to 4.51:1 — the number is a floor for the worst case, not a target.

Clean-room implementation — written from the described algorithm, not from source. Reuse policy

What it is

This page covers contrast in the narrow, measurable sense: a number computed from two colours’ relative luminance, used to check whether one will read legibly against the other. It is not about contrast in the loose design sense of “visually striking” or “high-contrast palette.” The corpus records this specifically as the W3C WCAG text-contrast ratio, also called the relative luminance contrast ratio, or chroma.contrast after the library that ships it. The formula that follows never looks at hue at all — it is built entirely from two lightness values — so a pair of colours can look punchy to the eye and still score badly here.

How it works

  1. Start from two colours, or from two already-derived lightness values — Programming Design Systems’ version takes the lightness values directly as its two arguments.
  2. Convert each colour’s lightness to its WCAG relative luminance: l1 and l2.
  3. Apply the ratio: (l1 + 0.05) / (l2 + 0.05).

Programming Design Systems implements exactly this as a two-argument function — function contrastRatio(l1, l2) { ... } — inside its chapter on colour schemes. chroma.js wraps the same computation behind chroma.contrast(color1, color2), taking two colours directly rather than pre-converted luminance values, and describes what it does plainly: “Computes the WCAG contrast ratio between two colors.”

Parameters & tuning

There are exactly two inputs, and they are not interchangeable: because the formula is a ratio rather than a difference, which colour you place as l1 versus l2 changes the result you get. This isn’t a knob for improving contrast — the function only reports what a candidate pair already is; it doesn’t push either colour toward a higher score. That constructive move — steer a colour toward a target luminance — is what luminance does instead, which is why the two are recorded here as neighbours rather than the same technique.

chroma.js’s own documentation gives the pass line: “a minimum contrast of 4.5:1.” Below that, neither source describes what the failure looks like on screen — no failure image or reading is given in either excerpt — so treat that as an open question rather than filling it in from general accessibility knowledge.

Where it’s been used

No specific artwork or artist is recorded here as having used this ratio. The two attested appearances are both reference implementations rather than artworks: Programming Design Systems’ colour-schemes chapter, where it’s implemented as a standalone function, and chroma.js — a BSD-licensed JavaScript colour library — where it ships as a general-purpose comparison method under the name chroma.contrast.

Variants & neighbours

Luminance solves the inverse problem: instead of measuring the ratio between two given colours, it adjusts one colour toward a target relative luminance by interpolating it toward black or white. APCA targets the same question — will this text read against this background — with a newer perceptual model, and instead of a single ratio number it maps straight to a minimum legible font size through a lookup table.

The hue-spacing schemes are a different axis entirely, and confusing them with this one is the mistake this page exists to head off. Complementary, triadic and tetradic schemes place hues 180, 120 or 90 degrees apart around the colour wheel; none of them reason about luminance at all, so a pair chosen for hue spacing can land anywhere on the WCAG scale depending on what lightness values get picked alongside those hues. The same gap applies to a HSL lerp gradient: it interpolates hue, saturation and lightness independently along a loop, and nothing about that interpolation computes or checks a contrast ratio at any point — one technique paints a transition, the other measures the legibility of an endpoint pair.

Go deeper

Connected to

Contrast with: luminance · APCA

Try it yourself

Pick two colours from a complementary scheme — two hues roughly 180 degrees apart, both at a similar mid-range lightness. Before computing anything, guess whether this pair passes chroma.js's stated minimum of 4.5:1. Then apply (l1+0.05)/(l2+0.05) to their luminances. What went wrong with the guess?

Hue separation and luminance separation are independent quantities. A complementary scheme (see the complementary colour scheme page) only constrains where the two hues sit on the colour wheel; it says nothing about how light or dark either one is. If both colours happen to sit at similar lightness, l1 and l2 are close together, the ratio (l1+0.05)/(l2+0.05) lands near 1, and the pair fails 4.5:1 despite looking 'high contrast' to the eye because the hues are opposed.


Revision 1 · 2 sources · Something wrong? Tell us.