Color Contrast & WCAG: A Practical Guide for Developers

Sourabh R.

Founder

6 min read

You're wrapping up a UI review when someone flags the placeholder text. "Does that gray pass WCAG?" You eyeball it. Looks fine to you. But running it through a WCAG color contrast checker tells a different story — 2.3:1 ratio. Fails AA by a wide margin. And you're not alone in missing it; about 300 million people have some form of color vision deficiency that makes low-contrast text genuinely hard to read.

What is a contrast ratio?

A contrast ratio is a number representing the difference in relative luminance between two colors. Pure white on pure black gives you 21:1 — the theoretical maximum. Two identical colors give a ratio of 1:1, the worst possible score. Most real-world text-on-background combinations land somewhere in between, and the goal is to stay above the AA threshold.

Luminance formula
L = 0.2126 × R + 0.7152 × G + 0.0722 × B
where R, G, B are linearized sRGB values
ratio = (L1 + 0.05) / (L2 + 0.05)

Accessible color ratio — AA vs AAA

  • AA — Minimum for compliance. Normal text needs 4.5:1. Large text (18px+ or 14px+ bold) gets a lower bar at 3:1.
  • AAA — Enhanced accessibility. Normal text needs 7:1. Large text needs 4.5:1.

For most products, AA is the realistic target. AAA for normal body text is genuinely hard to achieve while keeping a flexible visual design — you're essentially limited to near-black or near-white.

Common contrast failures worth auditing

  • Gray placeholder text on white — often lands around 2.8:1, which fails AA
  • Light blue links on white — Tailwind's blue-500 on white is 3.0:1, failing AA for normal text
  • Disabled button states — "unclickable" shouldn't also mean "unreadable"
  • White text over a light gradient or image background — fails at the bright end of the gradient

Color accessibility audit in seconds

The onHover Color Eyedropper — part of the Chrome extension — shows the WCAG contrast ratio as you pick colors. Sample the text color, then the background — the ratio and AA/AAA pass/fail grade appear immediately. No copy-pasting hex codes into a separate tool.

Design tokens that reliably pass

When building your color system, pre-validate text and background token pairs. These combinations clear AA without sacrificing too much design flexibility:

  • slate-200 on slate-900 — 12.6:1, passes AAA
  • white on blue-600 — 4.6:1, passes AA
  • slate-900 on amber-400 — 8.2:1, passes AAA

Color contrast isn't just an accessibility compliance checkbox to tick before launch. If your text is straining for someone with deuteranopia, it's probably also straining for everyone reading on a phone screen in direct daylight. Treating contrast as a quality signal — not just a legal requirement — is the right way to think about it.

Types of color vision deficiency that affect your users

The "300 million people" figure gets cited often, but it covers several distinct conditions, not one. Understanding the differences matters because they affect different parts of your UI in different ways.

Deuteranopia is red-green color blindness and the most common variant — roughly 6% of men have it. Greens and reds look similar. Status systems that rely on red-for-error and green-for-success without additional differentiation (icon, label, position) are the classic failure mode here.

Protanopia is red weakness — different from deuteranopia but in the same red-green spectrum. About 1% of men. The practical impact on UI is similar: color-coded status systems that don't pair color with a second signal fail for these users.

Tritanopia is blue-yellow deficiency. Rarer, but worth knowing if your brand is blue-heavy. Blue text on white and blue CTAs are specifically affected — and if your design system is built around a blue primary color (as many are), tritanopia is the one to test explicitly.

Achromatopsia — complete color blindness — is rare, but those users rely entirely on luminance contrast. No color differentiation at all. This is why the WCAG formula is luminance-based: a ratio that passes for standard vision passes for achromatopsia too, because it's measuring luminance difference rather than color difference. Contrast is the universal solution across all deficiency types. That's not a coincidence; it's why the spec is designed the way it is.

Testing contrast in dark mode

Dark mode adds a layer of complexity that most teams forget to check separately. The light mode audit passes, dark mode ships, and suddenly there are contrast failures nobody tested because everyone assumed dark mode was "just the inverted version."

White-on-slate-900 at 15:1 in dark mode? Not the concern. The concern is your semantic colors — status indicators, alert banners, badge text. Red-500 on white (light mode) has a different contrast ratio than red-500 on slate-900 (dark mode), because the luminance relationship changes depending on which background you're measuring against. Red is relatively bright on its own, which means it passes against dark backgrounds more easily than light ones. Green is the opposite — green-500 on white is fine, but green-500 on a very dark surface can pass or fail depending on the exact shade.

The specific colors to audit in dark mode:

  • Error red on dark card backgrounds
  • Success green on dark surfaces
  • Warning amber on dark backgrounds (amber tends to be bright enough to pass, but check anyway)
  • Muted/secondary text — the "lighter" shade of body text that works well at slate-400 on white but might not meet AA on dark surfaces

The onHover Color Eyedropper works on dark mode interfaces exactly the same way as light mode. Toggle dark mode on your product, open the onHover Chrome extension, sample the text and background colors directly from the rendered dark mode UI, and get the ratio. Run this as a separate pass from your light mode audit — don't assume the same token pairs behave the same way in both themes.

Dark mode audit checklist

Check every semantic color token pair in dark mode separately from light mode. Status colors (error, success, warning, info) are the highest risk — they're often defined as single values that weren't validated against dark surfaces during the token design phase.

Building an accessible color palette from the start

The most efficient approach is to validate color pairs when building the token system — not after the UI is built. After-the-fact contrast audits are real work: you find failures, you negotiate shade adjustments with designers, you re-test, you update components. The same work done upfront, at token definition time, is just a few minutes per pair and prevents the whole cycle.

The workflow we use: for each semantic color token pair — text on surface, primary on background, error on card, success on input — we record the contrast ratio next to the token definition. Any pair that doesn't pass AA for its intended use case gets a different shade before the token is adopted. No token pair enters the system without a validated ratio attached.

This validation-at-definition approach means you're never in a position where a designer or developer uses an "approved" token combination that silently fails accessibility. If the token is in the system, it passed. That constraint is the thing worth building into your design process.

Tools that make this easier during design: Accessible Palette (a web tool for building WCAG-validated palettes), the Contrast plugin in Figma, and the built-in accessibility checker in Figma for component-level review. Build the validation into the phase where tokens are created — remove it from the pre-launch checklist entirely by making failures impossible to introduce in the first place.

Automated contrast checking in CI

Manual audits with the onHover developer toolkit catch issues during active development. But accessibility regressions can also be introduced later, when tokens change, when components are updated, or when a third-party library update changes some rendered color value unexpectedly. That's where CI-level automation fills the gap.

Tools like Axe — built into Playwright and Cypress as plugins — can run contrast checks against your rendered components during CI. The workflow is straightforward: run your visual component tests with an accessibility audit at the same time. Contrast failures appear in the same CI results as functional failures. A PR that introduces a contrast regression gets flagged before merge, not after a user reports it.

The realistic scope for CI contrast checking: your component library. Storybook with the Axe addon runs contrast checks on every story. Playwright component tests can run Axe on any rendered component. These cover the cases where regressions are most likely — centralized components used across many pages.

CI automation doesn't replace manual checking with onHover's Color Eyedropper. Automated tools can only check what they can access programmatically — they miss dynamic states like hover, focus, and loading. They miss contrast issues that only appear over image backgrounds. They miss issues in content-driven pages where text color meets unpredictable background images. Manual checking covers those cases. CI automation covers component regressions. Both are part of the same system, doing different things.

If you're a designer who codes or works closely with a dev team, the onHover for Designers page covers the full set of tools built around visual QA and design-to-code handoff — including the Color Eyedropper used throughout this workflow.

Frequently asked questions

What is WCAG color contrast?
WCAG (Web Content Accessibility Guidelines) color contrast requirements define the minimum luminance ratio between text and its background to ensure readability for users with low vision, color blindness, or in bright ambient conditions. The WCAG 2.2 standard defines two conformance levels: AA (the legal minimum in many jurisdictions) and AAA (enhanced accessibility). Contrast is expressed as a ratio — for example, 4.5:1 means the lighter color is 4.5 times more luminous than the darker color.
What is the minimum contrast ratio required for WCAG AA?
WCAG AA requires a contrast ratio of at least 4.5:1 for normal text (under 18pt or 14pt bold) and 3:1 for large text (18pt or larger, or 14pt bold or larger). User interface components and graphical objects like icons need at least 3:1 contrast against adjacent colors. The AA level is the standard required by most accessibility laws including the ADA, Section 508, and the European EN 301 549.
What is the contrast ratio requirement for WCAG AAA?
WCAG AAA requires a contrast ratio of at least 7:1 for normal text and 4.5:1 for large text. AAA compliance is not required by most accessibility laws but is the target for maximum accessibility — it covers users with very low vision (around 20/80 visual acuity) who are not using assistive technologies. Many public sector and healthcare websites target AAA compliance voluntarily.
How do I calculate the contrast ratio of two colors?
The WCAG contrast ratio formula uses relative luminance: calculate the luminance of each color (L1 and L2) using the sRGB formula, then divide (max(L1, L2) + 0.05) by (min(L1, L2) + 0.05). Luminance uses a gamma correction step for each RGB channel. In practice, you don't calculate this manually — tools like onHover's Contrast Checker, the WebAIM Contrast Checker, or browser DevTools' color picker will calculate it instantly.
How do I check color contrast on a live website?
In Chrome DevTools, click on a color swatch in the Styles panel — the color picker shows a contrast ratio if you select a text element. For checking any two colors directly from the page, onHover's Contrast Checker lets you use the eyedropper to pick both foreground and background colors from any pixel on the page, then shows the ratio with WCAG AA and AAA pass/fail status instantly.
Does color contrast affect SEO?
Color contrast doesn't directly affect Google's ranking algorithm, but it affects Core Web Vitals indirectly — pages flagged by Lighthouse for accessibility issues often have lower Accessibility scores, which Google references in its quality signals. More practically, poor contrast increases bounce rate and reduces time-on-page, which are behavioral signals Google uses as indirect quality indicators. Accessibility improvements consistently produce measurable conversion and engagement improvements.
What colors fail WCAG contrast requirements most often?
The most common failures are: light gray text on white backgrounds (a common design pattern that consistently fails AA at smaller sizes), brand colors used as text on light backgrounds (many brand palettes are optimized for logos, not text readability), white text on medium-brightness colored backgrounds (yellow, green, cyan), and placeholder text that uses a very light gray against a white input background.
What is the new APCA contrast standard?
APCA (Advanced Perceptual Contrast Algorithm) is a next-generation contrast algorithm developed for WCAG 3.0. Unlike the current WCAG 2.x formula, APCA accounts for font weight, font size, and text polarity (light-on-dark vs dark-on-light) in a single score. It better reflects how human vision perceives contrast across different text sizes and weights. WCAG 3.0 is still a working draft and APCA has not replaced the WCAG 2.x requirements in law yet.