WCAG Color Contrast Explained: Ratios, AA, AAA and APCA
Low-contrast text is the single most common accessibility failure on the web, found on the large majority of home pages in annual audits. The rules that fix it are short, but the arithmetic behind them is not obvious, and the brand colors most teams start from fail more often than they pass. This guide explains how the contrast ratio is calculated, which thresholds apply to what, where the formula goes wrong, and how to repair a palette without losing its character.
The short version
- Body text needs a contrast ratio of at least 4.5:1 against its background (WCAG 2.2 level AA); large text needs 3:1.
- Icons, input borders, focus rings and chart lines need 3:1 against what is next to them.
- The ratio compares luminance, not hue: red on green can look vivid and still fail.
- Mid-tone brand colors such as sky blue, bright green and orange almost never pass as text on white; their darker shades usually do.
- APCA, the algorithm proposed for WCAG 3, predicts readability better, especially in dark mode, but WCAG 2.2 is the legal standard today.
How the contrast ratio is calculated
WCAG measures contrast with the relative luminance of each color: how bright it is to the eye, on a scale from 0 for black to 1 for white. Each sRGB channel is first converted from its gamma-encoded value to linear light, because the numbers in a hex code are not proportional to brightness. The three linear channels are then weighted by how sensitive the eye is to each: green contributes about 72% of perceived brightness, red about 21% and blue only 7%.
linear(c) = c / 12.92 if c ≤ 0.04045
= ((c + 0.055) / 1.055) ^ 2.4 otherwise (c = channel / 255)
L = 0.2126 × R + 0.7152 × G + 0.0722 × B (linear channels)
ratio = (L_lighter + 0.05) / (L_darker + 0.05)
The + 0.05 models the glare of ambient light reflecting off a screen, and stops the ratio running to infinity for pure black. The result runs from 1:1 for identical colors to 21:1 for black on white. Because the eye weights green so heavily, pure blue #0000ff on white reaches 8.6:1, while pure yellow #ffff00 on white manages only 1.07:1, even though both are fully saturated.
The ratio is a threshold, so it must not be rounded up. #767676 on white is 4.54:1 and passes; #777777, one step lighter, is 4.47:1 and fails. Tools that display 4.5 for the second one by rounding are giving a false pass.
Which threshold applies to what
| Success criterion | Level | Applies to | Minimum |
|---|---|---|---|
| 1.4.3 Contrast (Minimum) | AA | Text and images of text | 4.5:1 |
| 1.4.3 Contrast (Minimum) | AA | Large text | 3:1 |
| 1.4.6 Contrast (Enhanced) | AAA | Text | 7:1 |
| 1.4.6 Contrast (Enhanced) | AAA | Large text | 4.5:1 |
| 1.4.11 Non-text Contrast | AA | UI component boundaries, states, focus indicators, meaningful graphics | 3:1 |
Large text means at least 18 point, which is 24 CSS pixels, at a regular weight, or at least 14 point, about 18.66 pixels, in bold. A 20-pixel semi-bold heading is not large text, so it needs the full 4.5:1.
Some things are exempt: text that is purely decorative, text in a logo or brand name, text on an inactive (disabled) control, and incidental text that happens to sit in a photograph. Placeholder text in a form field is not exempt if it carries information the user needs, which is one more reason not to use a placeholder as the only label.
Level AA is what laws reference. In the United States, the Department of Justice's 2024 rule for state and local governments adopts WCAG 2.1 AA, and courts routinely use it in Americans with Disabilities Act cases. In the European Union, the European Accessibility Act has applied to many consumer-facing websites and apps since 28 June 2025, through the EN 301 549 standard, which incorporates WCAG 2.1 AA. Level AAA is a sensible target for long-form reading, but is not required across a whole site.
Why brand colors fail, and how to fix them
Designers pick brand colors for vividness, and vivid colors sit in the middle of the lightness range, which is exactly where contrast against both white and black is weakest. Some common examples on a white background:
| Color | Example use | Ratio on white | Body text |
|---|---|---|---|
#38bdf8 sky blue | Link color | 2.14:1 | Fails |
#0284c7 a darker sky | Link color | 4.09:1 | Fails (large text only) |
#0369a1 darker again | Link color | 5.93:1 | Passes AA |
#22c55e green | "Success" text | 2.27:1 | Fails |
#15803d dark green | "Success" text | 5.01:1 | Passes AA |
#ef4444 red | Error message | 3.76:1 | Fails |
#dc2626 darker red | Error message | 4.82:1 | Passes AA |
The same applies in reverse to white text on a colored button: white on #3b82f6 is 3.67:1 and fails for button labels, while white on #2563eb, one shade darker, is 5.16:1 and passes. The usual fix is not to abandon the brand color but to use it for large surfaces, borders and illustrations, and a darker shade of the same hue for text and small controls.
Darkening a color in HSL tends to make it muddy and shift its hue. Doing it in a perceptual space such as OKLCH, by lowering only the lightness while keeping chroma and hue, keeps the color recognizably the same. That is what the fix buttons in our color converter and contrast checker do: they search for the nearest OKLCH lightness that reaches 4.5:1 or 7:1, so #38bdf8 on white becomes #007fad at 4.52:1 rather than being replaced by black.
Common contrast pitfalls
- Transparency. Text set with
opacityor an alpha color is measured on the color it produces after blending. Black at 54% opacity on white is 4.58:1; at 50% it fails. Check the composited color, not the declared one. - Text over images and gradients. The ratio has to hold against the lightest part of the image behind the text. Use an overlay, a text shadow or a solid band behind captions.
- Hover, focus and visited states. Every state is text the user reads, so each needs its own check, and the focus indicator itself needs 3:1 against the surrounding colors.
- Disabled is not invisible. Disabled controls are exempt, but users still need to see that the control exists; a ratio around 2:1 to 3:1 keeps it discoverable.
- Color as the only signal. Contrast is about luminance; meaning conveyed by hue alone, such as red for an error with no icon or text, fails a separate criterion, 1.4.1 Use of Color.
Where the WCAG 2 formula goes wrong, and what APCA changes
The WCAG 2 ratio is simple and legally established, but it is known to misjudge two situations. It is too generous in dark mode: mid-grey text on black often passes comfortably while being harder to read than its score suggests. And it is too strict for some light, saturated colors in large sizes. Both problems come from treating contrast symmetrically, as if light text on dark were equivalent to dark text on light, which human vision does not do.
The Accessible Perceptual Contrast Algorithm (APCA), developed for the WCAG 3 drafts, fixes this by modelling perceived lightness contrast with different exponents for each polarity. It reports a value called Lc from 0 to about 106 for dark text on light and to about −108 for light on dark. Its guidance is tied to text size and weight rather than one pass mark: roughly Lc 90 for long body text, 75 as the minimum for body text, 60 for text larger than body size, 45 for large headings, and 30 for placeholder or disabled text.
A concrete example shows the difference. Light grey #959595 on black scores 7.01:1, enough for WCAG 2 level AAA. Its APCA value is only about Lc −45, which APCA considers suitable for large headings, not paragraphs. Readers of dark interfaces would agree with APCA. Until WCAG 3 is finished and adopted, the practical approach is to pass WCAG 2.2 AA for compliance and use APCA as a second opinion, especially for dark themes.
A checking routine that works
- List every text color and background pair in the design system, including states: default, hover, focus, visited, error, disabled, and both themes.
- Check each against 4.5:1, or 3:1 if it is only ever used for genuinely large text. Check icon colors and input borders against 3:1.
- For each failure, darken or lighten the foreground in OKLCH until it passes, and give the passing shade a token name such as
--brand-text, separate from the brand fill color. - Review the dark theme with APCA as well, and soften pure white text on pure black slightly (for example
#f1f5f9on#0a0c10), which reduces halation for readers with astigmatism while staying far above the threshold. - Automate it: contrast checks in a design-token build or in automated accessibility tests catch regressions before they ship.
Related guides and tools
Frequently asked questions
For body text, at least 4.5:1 against its background, which is WCAG 2.2 level AA and the level most accessibility laws require. Large text, meaning 24 pixels regular or about 18.7 pixels bold, needs at least 3:1. For long-form reading, aim for 7:1, the AAA level. Icons, form-field borders and focus indicators need at least 3:1 against adjacent colors.
They are two conformance levels. AA requires 4.5:1 for normal text and 3:1 for large text, and is the level referenced by laws and procurement rules in the United States, the European Union and elsewhere. AAA requires 7:1 and 4.5:1. W3C does not recommend requiring AAA across entire sites, because some content cannot meet it, but it is a good target for article text and documentation.
Yes, under WCAG 2.1 and 2.2 success criterion 1.4.11, Non-text Contrast. Icons that convey meaning, the visible boundary of a button or input, checkbox and radio states, focus indicators and the lines and bars of a chart need at least 3:1 against the colors next to them. A button's label text is still text, so it needs 4.5:1 against the button's fill.
Not yet. APCA was developed for WCAG 3, which is still a working draft and whose contrast method may change before it is finished. WCAG 2.2, with its contrast ratio, is the current W3C recommendation and the basis of legal requirements. Use the WCAG 2.2 ratio to confirm compliance and APCA as an additional check on readability, particularly for dark themes.
Keep the brand color for large areas, illustrations and borders, and introduce a darker shade of the same hue for text and small elements on light backgrounds, or a lighter one on dark backgrounds. Adjusting lightness in OKLCH rather than HSL keeps the hue and saturation looking the same. Name the accessible shade as a separate design token so it is used consistently.