Huely color system builder

Huely/Guides/WCAG color contrast

WCAG color contrast

Contrast is the one accessibility requirement that a color palette can fail entirely on its own. This is what the ratio measures, which thresholds apply to what, and how to repair a pair that misses one.

Published · Last reviewed

What the ratio measures

The WCAG contrast ratio compares the relative luminance of two colors — how much light each one reflects, after each sRGB channel is linearised and weighted 0.2126 red, 0.7152 green, 0.0722 blue. The ratio is (L1 + 0.05) / (L2 + 0.05) with the lighter color on top, which puts every possible pair between 1:1 (identical) and 21:1 (pure black on pure white).

Two consequences follow from those weights and catch people out. Green contributes roughly seven times as much luminance as blue, so #0000FF on white is a comfortable 8.6:1 while #00FF00 on white is 1.4:1 and unreadable. And hue and saturation are absent from the formula entirely: two colors on opposite sides of the wheel can be visually loud against each other and still fail, because contrast here means lightness difference, not difference in general.

The thresholds

WhatSuccess criterionAAAAA
Body text (under large)1.4.3 / 1.4.64.5:17:1
Large text1.4.3 / 1.4.63:14.5:1
UI components and meaningful graphics1.4.113:1
Focus indicator1.4.11 / 2.4.113:1
Logotypes, incidental text, disabled controlsexemptexempt

AA is the operative level. Section 508 in the United States, EN 301 549 in the EU, and the accessibility clauses in most public-sector procurement all reference WCAG level AA. AAA is a target for specific content, not a level the guidelines expect a whole site to meet.

What counts as large text

WCAG defines large text in points: 18pt, or 14pt bold. Converted at the default 16px root size that is 24px, or 18.66px bold. Anything under that is body text and needs 4.5:1, including the 18px subheadings and 20px card titles that designers routinely assume are covered.

The definition is in points rather than pixels on purpose: it tracks the user's font size. If your interface renders at a larger root size the threshold moves with it. In practice the safe habit is to treat 24px as the boundary and check anything near it explicitly rather than eyeballing.

Non-text contrast

Success criterion 1.4.11 was added in WCAG 2.1 and is the one most often missed, because it applies to things that are not text at all. Anything needed to identify or operate a control has to reach 3:1 against what is adjacent to it:

Text inside a control is still text and is measured against the control's own fill, not against the page. A button in #F26D5B with white text gives 2.9:1 — which fails body text at AA even though the button itself is clearly visible against the page.

What is exempt

Four categories carry no contrast requirement. Logotypes, because brand marks are what they are. Purely decorative graphics that convey no information. Incidental text — a name on a photograph of a shop front, for instance. And inactive controls: a disabled button is exempt from both 1.4.3 and 1.4.11.

The disabled exemption is easy to abuse. A control that is disabled and unreadable still leaves the user unable to work out why they cannot proceed. The exemption removes the legal requirement, not the design problem — pairing a low-contrast disabled state with a visible explanation is the better answer.

Repairing a failing pair

Contrast responds to lightness, so lightness is the only thing worth moving. Working in OKLCH makes this a one-dimensional search: hold C and h, move L until the ratio clears the threshold, and stop at the first value that does. Both directions are worth trying — for a mid-tone color, darkening is often a smaller change than lightening, or the reverse, and the smaller change is the one that stays closest to the color you chose.

Two habits keep the repair from feeling like a compromise. First, repair the text color rather than the background where you can: a page background is seen everywhere and a text color is seen only where that text is. Second, keep the original as the large-surface color and use the adjusted variant only where small text sits on it. A brand orange can stay exactly itself on a hero panel and appear one step darker as a link color without anyone reading it as two different colors.

Check a pair in the contrast checker

Five mistakes that pass a checker and fail a user

  1. Measuring against the wrong background. Text on a translucent overlay is measured against what is actually behind it, which on a photo is not one color. Either put an opaque layer behind the text or test against the lightest and darkest pixels the overlay can sit on.
  2. Forgetting opacity. color: #111; opacity: .6 is not #111. It composites to something near #6B6B6B on white, which is 5.3:1 rather than 18.9:1. Check the composited value, not the declared one.
  3. Checking one theme. A pair that passes on the light background can fail on the dark one; the two are separate measurements.
  4. Testing only the default state. Hover, active, visited, focus and error states each have their own colors and each has to pass.
  5. Relying on color to carry the meaning. Passing 4.5:1 says nothing about whether a red "error" and a green "ok" are distinguishable to someone with deuteranopia. That is criterion 1.4.1, a separate requirement — see the color blindness simulator.

Checklist

Frequently asked questions

What contrast ratio do I actually need?

4.5:1 for body text and 3:1 for large text meets WCAG 2.2 level AA, which is the level almost every accessibility law and procurement rule points at. Level AAA raises those to 7:1 and 4.5:1. Non-text elements that carry meaning — input borders, icons, focus indicators, chart series — need 3:1.

Is 18px bold text "large text"?

No. WCAG defines large text as 18 point or 14 point bold, which in CSS pixels at the default 16px root size is 24px, or 18.66px bold. 18px bold is under that line and needs the full 4.5:1. The rule scales with the user font size, so if your base is not 16px, convert from points rather than assuming the pixel values.

Does contrast apply to disabled buttons and placeholder text?

Disabled controls are explicitly exempt from 1.4.3 and 1.4.11, so a greyed-out button does not have to pass. Placeholder text is not exempt: it is text presented to the user, and if it carries information — a format hint, an example value — it needs 4.5:1 like any other body text. The safer pattern is to move that information into a visible label or hint below the field.

How do I fix a failing pair without changing my brand color?

Change lightness, not hue. Contrast is driven almost entirely by relative luminance, so lightening or darkening a color while holding its hue and chroma reaches the threshold with the least visible drift. Keep the original hue for large surfaces such as buttons and hero fills, and use the adjusted variant only where it carries small text.

Is APCA replacing the WCAG contrast ratio?

Not yet. APCA is under development as candidate guidance for a future version of WCAG, and it models thin light-on-dark text more accurately than the 2.x ratio does. As of WCAG 2.2 the normative requirement is still the relative-luminance ratio, so conformance claims have to be made against that. Treat APCA as a useful second opinion, not as a substitute.

Sources

Next

Contrast checkerMeasure any pair against all four thresholds and repair a failing one. Accessible color palettesBuild a palette that passes instead of repairing one that does not. Color blindness simulatorContrast is not the whole of 1.4.1 — check what collapses. Dark mode palettesWhy the same pair behaves differently on a dark background.