BlogDesign

Color contrast and WCAG: passing accessibility checks

How WCAG color contrast ratios are computed, the AA and AAA thresholds for text and UI, the failures audits flag most, and where WCAG 2 math falls short.

Color contrast is the single most-failed accessibility requirement in real audits. WebAIM's annual scan of the top million home pages has flagged low-contrast text on more than 80% of them, year after year. It fails because the math is invisible: a foreground and background that look fine to a designer on a calibrated monitor in a dim room can be unreadable to a low-vision user, to anyone outdoors in glare, or to someone on a cheap panel with a crushed gamma curve. WCAG gives you a number that takes the guesswork out, but the number is easy to get wrong and easy to misunderstand. This post covers how it is computed, the thresholds that matter, and where the model breaks down.

The contrast ratio

WCAG expresses contrast as a ratio between two colors, ranging from 1:1 (two identical colors, no contrast) to 21:1 (pure black on pure white, the maximum). The ratio is computed from the relative luminance of each color with this formula:

(L1 + 0.05) / (L2 + 0.05)

where L1 is the relative luminance of the lighter color and L2 that of the darker one. The 0.05 term is a flare constant that models ambient light reflecting off a screen — without it, the ratio against pure black would run to infinity.

Relative luminance is not the raw RGB value. Each channel is first normalized to the 0–1 range, then linearized to undo the sRGB gamma curve (a transfer function that reverses the encoding monitors apply), then the three linear channels are weighted and summed. The weights are not equal: green contributes the most to perceived brightness, red is in the middle, and blue contributes the least. That is why two colors with the same lightness in a naive sense can have very different luminance — swapping a blue accent for a green one of the same "intensity" can move you across a threshold.

You rarely compute this by hand. The point of understanding the shape is knowing why a color picker's lightness slider is not a contrast control: lightness and luminance are related but not the same, and the weighting is what makes contrast checking non-obvious.

The thresholds

WCAG 2.1 defines pass/fail thresholds at two conformance levels, with different bars for text size and for non-text elements.

Element AA AAA
Normal text 4.5:1 7:1
Large text 3:1 4.5:1
UI components & graphical objects 3:1 (no AAA requirement)

"Large text" has a precise definition: at least 18pt (roughly 24px), or 14pt bold (roughly 18.66px bold). Anything smaller is "normal text" and must clear the higher 4.5:1 bar at AA. The size threshold exists because larger glyphs have more stroke area, so a given contrast ratio is easier to read at scale.

The third row comes from WCAG 2.1's success criterion 1.4.11 (Non-text Contrast). It covers the boundaries of UI controls — input borders, button outlines, toggle states, focus indicators — and the meaningful parts of informational graphics like chart lines or icon shapes. These need 3:1 against their adjacent colors. This criterion is newer than the text rules and is widely overlooked: a form whose text passes can still fail because its input fields have a 1.5:1 hairline border.

AA is the conformance level cited in most legal and procurement contexts (the EU's EN 301 549, the US Section 508 refresh, and most corporate accessibility policies). AAA is aspirational for general content; WCAG itself notes that AAA conformance across a whole site is often not achievable.

The failures audits flag most

A handful of patterns account for the bulk of contrast violations.

  • Light-gray placeholder and helper text. #999 on white is a near-universal default for placeholders and captions. It computes to about 2.85:1 — a clear AA failure for normal text. Placeholder text is real content and is judged by the same bar.
  • Brand colors that look fine. Mid-tone brand palettes are the classic trap. A saturated brand orange or sky blue reads as confident and legible to the team that chose it, then fails 4.5:1 against white by a hair. The color is not "wrong"; it just can't carry body text at that pairing.
  • Text over images and gradients. A hero headline sits on a photo. The contrast is fine over the dark third of the image and fails over the bright third. Audits and automated tools struggle here because the background is not a single color.
  • Disabled states. Disabled controls are explicitly exempt from the contrast requirements. That is a conformance technicality, not a usability win — a disabled button so faint nobody can read it is still a bad experience, and users often can't tell "disabled" from "broken."
  • Hover and focus states. Teams check the resting state of a link or button and forget that the hover, active, and focus styles each introduce a new color pairing that must also pass. Focus indicators in particular fall under the 3:1 non-text rule.

Practical fixes

The fixes are usually small and local.

The most common is to nudge one color. Darkening a too-light gray from #999 to about #767676 clears 4.5:1 on white with no visible change in character. For a brand color that fails, you rarely need to abandon it — a few percent darker for text use, while keeping the original for large display type or decorative fills, is enough. A quick way to iterate is to tweak the value in one notation and read it back in another with our Color Converter, then check the result.

For text over images, add a scrim: a semi-transparent dark (or light) layer between the image and the text, or a gradient overlay behind just the text block. This guarantees a known background luminance regardless of what the photo is doing underneath.

Finally, do not rely on color alone. This is a separate WCAG criterion (1.4.1, Use of Color), not a contrast rule, but it travels with the same work. Error fields marked only by turning red, links distinguished from body text only by hue, chart series identified only by color — all fail for users who can't perceive the color difference. Add an icon, an underline, a label, or a pattern alongside the color.

Where the model breaks down

Be honest about what the WCAG 2 contrast formula is: a blunt instrument. It was designed to be simple to compute, and it does not model human perception especially well. It is known to be too lenient on some dark-mode pairings (light text on dark backgrounds can pass the ratio while looking thin and hard to read) and too strict on certain light-on-light combinations. Font weight and size beyond the single "large text" cutoff don't enter into it at all, even though a thin hairline font and a heavy grotesque at the same color pairing read very differently.

The proposed replacement is APCA (the Accessible Perceptual Contrast Algorithm), the contrast method being developed for WCAG 3. APCA accounts for the polarity of the pair (dark-on-light versus light-on-dark behave differently for the eye) and factors in font weight and size. It is a meaningful improvement, but it is still a draft: WCAG 3 is years from being a recommendation, and APCA is not normative. If you have a conformance obligation today, you test against WCAG 2.1's 4.5:1 / 3:1 numbers. Treat APCA as a useful sanity check for borderline cases, not as the standard you cite.

For checking a specific foreground/background pair against the AA and AAA thresholds — including the large-text and UI-component bars — our Color Contrast Checker computes the ratio and tells you which levels it passes, so you can settle a borderline color before it shows up in an audit.