Accessibility · Perception

Perceptual Contrast Models and Why Weight and Polarity Change the Verdict

WCAG 2 contrast is a ratio between two colors; newer models predict lightness difference from a background and scale it by text size and weight, which makes polarity and stroke thickness part of the result.

The WCAG 2 contrast ratio is a symmetric function of two luminances. It returns the same number whether the lighter color is the text or the background, and it does not know whether the text is 12 px regular or 40 px bold. Those two omissions are the substance of the long-running argument about whether the metric measures what readers experience, and they are why a second family of models exists.

Two properties the ratio cannot express

The first is polarity. In the ratio, black on white and white on black at the same luminance separation score identically. Perceptually they do not behave identically. Light text on a dark ground produces halation: the bright glyph’s edge spreads into the surrounding dark, which makes the glyph look thicker and, at small sizes, softer. Dark text on a light ground produces the opposite, a slight thinning of the stroke, which reads as sharper. At the same computed ratio, light-on-dark text is harder to resolve at small sizes and easier to resolve at large ones, and the crossover is real.

The second is stroke geometry. Contrast between ink and paper determines how much signal reaches the retina at the edge of a glyph; the width of the stroke determines how much of that signal survives the eye’s own spatial filtering. A 200-weight face at 16 px has stems around 1.5 device-independent pixels wide on a typical display, while a 700-weight face at 32 px has stems of five or more. The wide stroke has a large plateau of pure ink, and the contrast at the edges matters less to the overall percept. This is the mechanism behind the large-text exemption in WCAG, but the exemption is a two-tier step function applied after the ratio is computed, not a term in the ratio.

What a lightness-difference model does instead

The newer models compute a predicted lightness contrast between the text color and the background, in a perceptual color space rather than a luminance ratio, and then apply a lookup that depends on font weight and size to produce a required minimum. The output is signed: a positive value for dark text on light, a negative value for light text on dark, with the two signs having different acceptance thresholds. A light-on-dark pair therefore needs more predicted contrast than the equivalent dark-on-light pair at the same size, which is the polarity asymmetry made explicit.

The dependence on weight is the more consequential change for design systems. Under the ratio, one token can serve every text size as long as it clears 4.5:1. Under a weight-aware model, a 300-weight body face may need a considerably darker ink than a 700-weight heading face to reach the same predicted contrast, so a palette has to be evaluated per typographic role rather than once. Systems that define a single “muted text” color and use it at every size find that the token is adequate for labels and inadequate for long-form body copy, or the reverse depending on which way the threshold is set.

The colour space choice matters too. A perceptual model that operates in a space with a lightness component similar to CIE Lab or Oklab is comparing a quantity that is not luminance. Two colors with identical relative luminance can have different lightness in a perceptual space, because the space accounts for chroma and for the eye’s adaptation. This is why a saturated blue and a mid gray of the same computed luminance can score differently under a perceptual model: the blue is chromatically much further from the background even though its luminance is the same.

// Illustrative shape of a signed lightness-difference model, not any
// published algorithm: the exponent compresses large deltas, the sign
// preserves polarity so that the two directions can have separate limits.
function lightnessContrast(textY, bgY) {
  const polarity = Math.sign(bgY - textY);      // +1 dark on light, -1 light on dark
  const delta = Math.abs(bgY - textY);          // Y in a perceptual space, 0..1
  return polarity * Math.pow(delta, 0.6) * 100; // scaled to a notional 0..100 range
}

The exponent is what makes the model behave differently near the ends of the range. A power below 1 compresses large differences and expands small ones relative to a linear mapping, so near-black on near-white does not run away to an implausible score and the crowded middle of the range is where the model has resolution. The practical effect is that the difference between 7:1 and 12:1 under the ratio becomes a modest difference under a perceptual model, while the difference between 3:1 and 4.5:1 becomes larger. The two metrics disagree most in the region where most designs actually live.

Where the two metrics disagree, and why that is not a bug

The disagreements are systematic rather than random. Saturated colors on mid-tone backgrounds are penalised more by a luminance-ratio metric than by a perceptual one, because the ratio sees only luminance and the perceptual model sees the chroma. Very dark text on a dark background is penalised more by the perceptual model, because it accounts for the eye’s reduced ability to resolve small lightness differences at low levels, a region where the ratio’s additive constant compresses everything into a narrow band of ratios near 1.

Neither model is a measurement of an individual’s vision. Both are fitted to populations, and both are simplifications: they assume a particular rendering stack, a particular font, and a particular viewing distance. A ratio of 4.5:1 and a perceptual score at the corresponding threshold are both claims about a median observer under median conditions, and neither predicts whether a specific person with a specific cataract will read a specific glyph.

That is the honest framing for the adoption question. The older metric is normative, is cheap to compute, and is what conformance testing uses; any tool that reports a pass or fail today is almost certainly reporting the ratio. The newer models are better predictors of reading performance at the margins and are being incorporated into drafts and into design tooling, but they are not yet the basis of a conformance requirement. A design system can compute both and treat the stricter result as the floor, which is the only option that does not require choosing which population to serve less well.

Implementing a dual check

The mechanics are simple: one function per metric, both fed the same composited colors, and a policy that says which wins. The hard part is the same as in any contrast work, namely getting the composited colors rather than the declared ones, since a translucent text color or a text shadow changes what is on screen without changing the declared value. Alpha compositing happens in the encoded space in the common rendering path, which is the behaviour described in the luminance computation and is a frequent source of disagreement between a browser and an offline checker.

Text shadows deserve a specific note because they are a common attempt to rescue marginal contrast. A dark shadow behind light text raises the local contrast at the shadow’s offset but leaves the rest of the glyph against the original backdrop, so it improves the average and not the minimum. A shadow that fully surrounds the glyph, with zero offset and a large blur, is closer to a scrim and does raise the effective contrast, at the cost of a fuzzy edge that hurts resolution at small sizes.

The dual-check policy also has to decide what to do about non-text elements, which the perceptual literature treats less thoroughly than text. Focus rings and icon strokes are thin, which is precisely the geometry that weight-aware models were built to reason about, yet the published thresholds for them are still expressed as ratios. Applying a perceptual model to a 2 px ring is defensible and produces a stricter requirement than 3:1 in most cases, which is either the right call or an over-correction depending on how much confidence the model’s thin-stroke behaviour deserves.

References

  1. Web Content Accessibility Guidelines (WCAG) 2.2 — W3C
  2. Visual Contrast of Text Subgroup Report — W3C Low Vision Accessibility Task Force
  3. Accessible Perceptual Contrast Algorithm (APCA) — SAPC / APCA project
  4. CSS Color Module Level 4 — W3C