WCAG Color Contrast Explained: Making the Web Accessible
What the WCAG 2.2 ratios mean per the W3C spec, how to test them, and easy fixes for failing designs.
What WCAG Is and Why Contrast Matters
The Web Content Accessibility Guidelines (WCAG) are the international standard for web accessibility, published by the World Wide Web Consortium (W3C) Web Accessibility Initiative. The current version is WCAG 2.2, which became a W3C Recommendation in October 2023 and supersedes WCAG 2.1. The standard organizes accessibility guidance into four principles — content must be Perceivable, Operable, Understandable, and Robust — and within each principle are testable Success Criteria at three conformance levels: A (lowest), AA (the legal minimum in most jurisdictions), and AAA (the highest, not always achievable).
Color contrast is Success Criterion 1.4.3 (Minimum Contrast) at Level AA and 1.4.6 (Higher Contrast) at Level AAA. The requirement is about the contrast between text and its background: if the contrast is too low, users with low vision, color vision deficiencies, or simply viewing the page in bright sunlight cannot read the text. The European Union's European Accessibility Act (effective June 2025) and the United States Department of Justice's ADA Title II rule (effective April 2026 for state and local government websites) both reference WCAG 2.1 AA as the legal minimum, and most jurisdictions are expected to upgrade to 2.2 over the coming years.
The Contrast Ratio Formula
WCAG defines contrast as a ratio between two colors' relative luminance. The formula is:
ratio = (L1 + 0.05) / (L2 + 0.05)
where L1 is the relative luminance of the lighter color and L2 is the relative luminance of the darker color. The 0.05 offset accounts for ambient flare (the small amount of light even a perfectly black surface reflects in a real viewing environment), and it ensures the ratio never divides by zero.
Relative luminance itself is defined in the normative WCAG definition as a weighted sum of the sRGB color channels, with a gamma decode applied to each channel to linearize it:
L = 0.2126 × R + 0.7152 × G + 0.0722 × B
The weights 0.2126, 0.7152, and 0.0722 reflect the human eye's sensitivity to each color channel: green is perceived most strongly, then red, then blue. Before applying the weights, each sRGB channel value (0 to 1) must be gamma-decoded:
if c <= 0.03928: c_linear = c / 12.92
else: c_linear = ((c + 0.055) / 1.055) ^ 2.4
The 0.03928 threshold and 2.4 exponent come from the sRGB color space specification (IEC 61966-2-1). The result is a relative luminance value between 0 (pure black) and 1 (pure white) that matches how the human eye actually perceives brightness, rather than the linear RGB values stored in the file.
The Thresholds
WCAG 2.2 defines four threshold values:
- AA for normal text (Level AA, SC 1.4.3): at least 4.5:1. "Normal text" is anything smaller than 18 point (24 px) regular or 14 point (18.66 px) bold.
- AA for large text (Level AA, SC 1.4.3): at least 3:1. "Large text" is 18 point regular or larger, or 14 point bold or larger. Large text is held to a lower threshold because the larger glyph area makes lower-contrast text still legible.
- AAA for normal text (Level AAA, SC 1.4.6): at least 7:1.
- AAA for large text (Level AAA, SC 1.4.6): at least 4.5:1.
A separate criterion, SC 1.4.11 (Non-text Contrast, Level AA), requires a 3:1 contrast ratio for user interface components and graphical objects — borders of form inputs, focus indicators, icons that convey information, and chart data series. This criterion exists because users need to perceive the boundaries of interactive elements, even when no text is involved.
Worked Examples
Consider white text (#FFFFFF) on a brand-blue background. The brand blue is #3B82F6, a common Tailwind blue. Computing relative luminance: the sRGB channels are (0.231, 0.510, 0.965) after dividing the hex values by 255. Applying the gamma decode to each and summing with the WCAG weights gives a relative luminance of approximately 0.224 for the blue, and exactly 1.0 for white. The contrast ratio is (1.0 + 0.05) / (0.224 + 0.05) = 1.05 / 0.274 = 3.83. This fails AA for normal text (needs 4.5:1) but passes for large text (needs 3:1). To pass AA for normal text, the brand blue needs to be darkened to roughly #1D4ED8, which yields a ratio of about 6.5:1.
Now consider the common failure case: light gray text on white. #999999 on #FFFFFF. The gray's relative luminance is approximately 0.299, giving a contrast ratio of 1.05 / 0.349 = 3.0. This passes AA for large text but fails for normal text, and many designers use it for body copy — the most common WCAG violation on the web. Darkening the gray to #767676 raises the ratio to 4.54:1, passing AA for normal text. #595959 raises it to about 7.0:1, passing AAA.
For brand-colored text on white, the same calculation applies. A common choice like #3B82F6 blue text on white fails AA (ratio approximately 3.0:1) and must be darkened for body text use; the same color works fine for large headings or for icons paired with a label.
How to Test
The fastest way to test contrast is a dedicated checker. WebAIM's Contrast Checker at webaim.org/resources/contrastchecker is a free, browser-based tool that accepts two hex codes and returns the ratio plus pass/fail for each WCAG level. The W3C's own checker at contrast-ratio.com is similar. Most design tools now include built-in checkers: Figma has a contrast plugin ecosystem, Adobe XD has native contrast checking, and Chrome DevTools (in the Elements panel, under the color picker) shows contrast ratios against the computed background in real time.
For automated batch checking of an entire site, the axe DevTools browser extension (Deque) and the Lighthouse audit built into Chrome both run contrast checks across every visible text element on the page and report failures. Lighthouse includes contrast as part of its Accessibility score; an axe scan produces a more detailed report including the specific elements that fail and the WCAG criterion they fail.
For programmatic testing in a continuous integration pipeline, the axe-core npm package can run the same checks against a headless browser, failing the build when contrast drops below AA. The pa11y CLI tool wraps similar checks in a developer-friendly interface. Both can be integrated into a GitHub Actions workflow so contrast regressions are caught before deployment.
Common Mistakes and Fixes
- Light gray on white for body text. The most common WCAG violation. Fix: darken to
#767676or below, or use a tinted dark gray like#1F2937that has more visual weight. - Brand color used directly for text. Brand colors are typically chosen for vibrancy against a white surface as a fill, not as text. Fix: define a "text-safe" variant of the brand color that meets 4.5:1, and use it specifically for text on white.
- White text on brand color without checking. Many brand colors fail 4.5:1 with white text. Fix: darken the brand color for backgrounds, or use black text on a light brand color instead.
- Placeholder text using low-contrast gray. HTML placeholders are governed by the same criteria as body text. The common
color: #999for placeholders fails AA. Fix: use#767676or darker for placeholders, or replace placeholders with always-visible labels. - Low-contrast focus indicators. SC 1.4.11 requires 3:1 contrast for focus indicators, which are often overlooked because they only appear on keyboard navigation. Fix: use a focus ring that contrasts with both the element and the page background; the default browser focus ring usually meets this, custom rings often do not.
- Forgetting disabled states. Disabled buttons and inputs are often rendered in low-contrast gray. SC 1.4.3 exempts "inactive user interface components," so this is technically allowed, but if the disabled state must be readable, hold it to 3:1 minimum.
Conclusion
WCAG contrast is a mathematically defined standard: the ratio of two colors' relative luminance, with relative luminance computed from gamma-decoded sRGB values. The AA threshold of 4.5:1 for normal text and 3:1 for large text is the legal minimum in most jurisdictions and the practical baseline for readable web content. Testing is fast and free — WebAIM's checker for one-off checks, axe or Lighthouse for page-wide scans, axe-core or pa11y for CI integration. The common failures are well-known: light gray on white, brand colors used directly for text, and overlooked focus indicators. None of the fixes require sacrificing visual design; they require picking colors that work for users who do not have perfect vision in perfect lighting, which is most users most of the time. Written by the HT99 Tools Editorial Team.
Try the Tool This Article Explains
Put what you've learned into practice with our free, accurate calculators.