WCAG Color Contrast: A Checklist for Accessible Hex Palettes

I've reviewed a lot of design handoffs over the years, and there's a pattern: someone picks a gorgeous light-gray text on a white card, calls it "clean," and ships it. Then a user with low vision files a support ticket three weeks later because they literally cannot read the button label. WCAG contrast requirements aren't bureaucratic red tape — they're the difference between a product that works for everyone and one that quietly excludes millions of people.

This checklist is structured so you can move through it in one sitting. Bookmark it. Print it. Run through it before every launch.


Part 1: Understand What You're Actually Measuring

Before you start checking hex codes, get the terminology straight. WCAG measures relative luminance — a formula that converts RGB values into a perceptual brightness number between 0 (pure black) and 1 (pure white). The contrast ratio is computed as:

(L1 + 0.05) / (L2 + 0.05)

…where L1 is the lighter color and L2 is the darker one. A ratio of 1:1 means identical colors; 21:1 is pure black on pure white.

  • Check: Do you know the difference between AA and AAA? AA is the minimum acceptable threshold for most production work. AAA is the enhanced standard — harder to achieve but worth pursuing for body text.
  • Check: Are you testing rendered colors, not just design-file swatches? Opacity, background blending, and screen rendering can all shift your actual displayed color away from the raw hex value.
  • Check: Is your team clear that these ratios apply to text against its immediate background — not the page background two layers up?

Part 2: The Core Contrast Thresholds (WCAG 2.1)

Tape these numbers somewhere. They come up constantly.

Normal Text (below 18pt / below 14pt bold)

  • AA minimum: 4.5:1
  • AAA target: 7:1

Large Text (18pt+ regular, or 14pt+ bold)

  • AA minimum: 3:1
  • AAA target: 4.5:1

Non-Text UI Components (icons, input borders, chart lines)

  • AA minimum: 3:1 against adjacent colors
  • AAA has no formal threshold for non-text — but aim for 4.5:1 anyway.

Decorative elements and logos

  • Exempt from contrast requirements — but document this decision explicitly so auditors don't flag it.

Now run through these checks for every text/background pair in your palette:

  • ☐ Primary body text on default background
  • ☐ Secondary / muted text on default background
  • ☐ Text on primary brand color (e.g., white text on your brand blue button)
  • ☐ Text on secondary brand color
  • ☐ Error text on error background (those red-on-pink combos often fail)
  • ☐ Success text on success background
  • ☐ Warning text on warning background (yellow backgrounds are notorious)
  • ☐ Placeholder text in form inputs
  • ☐ Disabled state text — yes, even disabled elements should aim for 3:1 minimum for usability
  • ☐ Link text against body text (if not underlined, needs 3:1 against surrounding text AND 4.5:1 against background)
  • ☐ Focus indicator color against its background

Part 3: Convert Your Hex to Luminance (The Manual Path)

You don't always have a contrast checker handy. Here's the math so you can sanity-check a pair in your head or in a spreadsheet:

  1. Take your hex color, e.g. #4A90D9
  2. Convert each channel to 0–1 range: R = 74/255 ≈ 0.290, G = 144/255 ≈ 0.565, B = 217/255 ≈ 0.851
  3. Apply gamma correction: for each channel, if value ≤ 0.04045 divide by 12.92; otherwise compute ((value + 0.055) / 1.055) ^ 2.4
  4. Combine: L = 0.2126R + 0.7152G + 0.0722B
  5. Plug both L values into the ratio formula above
  • ☐ Do you have a spreadsheet template or script that does this conversion automatically for your design tokens?
  • ☐ If you're using a color system (HSL-based, or a numeric scale like Tailwind's 50–950), have you mapped which numbered stops reliably hit AA against white and which hit AA against black?

A useful rule of thumb with numeric scales: on a 50–950 scale against pure white, you typically need to be at 500 or darker to pass AA for normal text. Against pure black, 400 or lighter. But always verify — this shifts based on hue.


Part 4: Common Palette Traps

These are the combinations that fail most often in real-world audits. Check each one explicitly — don't assume they pass.

  • Light gray on white — beloved by minimal designers, routinely fails. #767676 on white is the borderline AA case (exactly 4.54:1). Go lighter and you fail.
  • Saturated yellow on white — even deep yellows like #F5A623 often only hit 2.8:1 against white. Never use yellow body text on light backgrounds.
  • Medium blue on white — popular brand blues around #4A90D9 typically land around 3.5:1, passing for large text only.
  • Green on white — greens are perceptually bright. Even relatively dark greens like #3D8B37 can fail AA for normal text.
  • Red error text on white — many "standard" error reds like #E53E3E fail normal-text AA (around 4.3:1). Check yours.
  • White text on medium-opacity overlays — semi-transparent dark overlays on variable photography. The effective contrast depends entirely on what's underneath. Either test worst-case or increase opacity.
  • Colored text on colored background (both saturated) — two mid-luminance colors can have a misleading visual contrast that simply doesn't translate to a passing ratio. Always run the math.

Part 5: Tooling Checklist

Running these checks manually for every pair doesn't scale. Build the right tooling into your workflow.

  • In-browser checker: WAVE (WebAIM), axe DevTools, or Colour Contrast Analyser desktop app. Use at least one for spot-checking rendered pages.
  • Design tool integration: Figma has Contrast (by Stark) and A11y - Color Contrast Checker plugins. Run these before handoff, not after.
  • CI/CD integration: axe-core with Playwright or Cypress can catch new contrast failures automatically on every pull request.
  • Design token validation: If you use Style Dictionary or similar, add a build-time step that rejects any text-on-background token pair below 4.5:1.
  • Browser devtools: Chrome DevTools shows contrast ratio in the color picker when you inspect text. Habit: check it while you inspect any text element you're unsure about.

Part 6: Edge Cases That Bite

  • Dark mode: Your dark-mode palette is a completely separate set of pairs. White text on dark backgrounds often passes easily, but secondary text (gray on dark gray) frequently fails. Audit both themes independently.
  • Text over images or gradients: WCAG doesn't have a clean formula for variable backgrounds. Best practice: test the lightest part of a gradient; add a text-shadow or overlay if needed.
  • Antialiasing and subpixel rendering: Thin, light text at small sizes can read worse than the ratio suggests. If a pair is right at the 4.5:1 borderline, consider nudging the color darker rather than living at the edge.
  • High-contrast mode (Windows): Some users run forced colors via the CSS forced-colors media query. Test that your UI doesn't break in this mode, even if it's technically outside WCAG's core requirements.
  • Hover and focus states: These are interactive, so they need to pass the same thresholds as normal interactive elements. A focus ring that's only visible on some backgrounds doesn't count.
  • Charts and data visualization: WCAG 1.4.11 applies to meaningful UI components. If your chart uses color alone to distinguish data series, that's also a separate accessibility failure (not just contrast).

Part 7: Before You Ship — Final Sign-Off

Run through this block as a literal sign-off task before any major release:

  • ☐ All body text pairs audited and documented with their actual ratios
  • ☐ All interactive element states (default, hover, focus, disabled, error) checked
  • ☐ Dark mode palette audited separately
  • ☐ Automated contrast tests passing in CI
  • ☐ Any exemptions (decorative, logo) explicitly documented with rationale
  • ☐ A real user with low vision, or a screen reader user, has tested the UI — tooling catches ratios but not everything that affects legibility

One last thing worth internalizing: WCAG compliance is a floor, not a ceiling. A 4.5:1 ratio is the minimum most people with mild vision impairment need. Aiming for 7:1 on your body text doesn't just help users — it makes your design more legible for everyone in bright sunlight, on cheap screens, and in ten years when their eyes have changed. Build that in from the start, and you stop treating accessibility as a retrofit problem.