Update

Gradienty is now GradientDeck. New home: gradientdeck.com

Open
← Back to Blog
Accessibility2026-07-01·9 min

WCAG Contrast for Designers: A Practical Guide (Not Just a Ratio Calculator)

Contrast ratios explained without jargon. What 4.5:1 actually means, how to test it, the five most common failures, and accessible alternatives you can copy. With live examples.

Every designer has heard '4.5:1 contrast ratio.' Most don't know what it actually means, why it's 4.5 and not 5, or how to fix a failing pair without making everything black-on-white. This guide explains contrast practically — with live examples you can inspect.

What the ratio actually means

The WCAG contrast ratio is the difference in relative luminance between two colors, expressed as a ratio from 1:1 (no contrast) to 21:1 (black on white). The formula is (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter color and L2 is the darker.

The 0.05 offset is the important part — it accounts for ambient light reflection. Without it, pure black (#000) on pure black would be 0/0 = undefined. With it, it's 0.05/0.05 = 1:1. And pure black on pure white is (1 + 0.05) / (0 + 0.05) = 21:1.

The four thresholds

Level Body text Large text What counts as 'large'
AA (minimum)4.5:13:1≥18.66px bold OR ≥24px regular
AAA (enhanced)7:14.5:1Same as above
UI components & icons3:13:1Borders, icons, focus rings
DisabledExemptExemptBut must still be distinguishable

AA is the legal minimum in most jurisdictions (ADA, EAA, AODA). AAA is a target, not a requirement. Aim for AA on everything, AAA on body text where possible.

The five most common failures

1. Muted text on a tinted surface

This is the #1 failure. A card switches to a slightly darker background, but the text inherits the default muted color — which was tuned for white, not for the darker surface.

FAIL (2.8:1)

Muted text on a card with a tinted background. The muted color was tuned for white.

PASS (7.2:1)

Same background, darker text. The card surface didn't change — the text color did.

The fix: whenever a surface changes, re-check the text color against it. The muted color that passed on white may fail on slate-50, slate-100, or a colored card.

2. Button text that matches the button fill

The model picked a text color and a fill color from the same palette family. They're 'close enough' visually — but 1.2:1 contrast means the text is invisible.

The fix: whenever a colored surface carries text, define a separate 'on-color' text variable (--color-accent-ink) and verify it passes 4.5:1 against the fill. Don't reuse the body text color.

3. Dark section with inherited dark text

A section switches to a dark background, but nested elements still use the default ink-colored text. The text is there — you just can't see it.

FAIL (1.0:1) — text is the same color as the background.

PASS (17.9:1) — text flipped to the light surface color.

The fix: any CSS rule that sets background to a dark color must also set color to a light color in the same rule — or be wrapped in a parent that does.

4. Placeholder text with no contrast

Placeholder text is often set to a very light gray, assuming it's 'less important.' But WCAG requires 3:1 for UI components — and placeholders are UI.

5. Focus rings that don't contrast against the page

A focus ring clears 3:1 against the element it's on — but not against the page surface behind the element. If the ring is blue and the page is blue-tinted, the ring disappears.

The fix: always use outline with outline-offset, and test the ring color against both the element AND the page surface. If in doubt, use a 2px ring with a 2px white inner offset (the white creates separation from any background).

How to actually test contrast

  1. Identify every (text-color, background-color) pair in your design. Not just the obvious ones — check muted text, placeholder text, labels, captions, and icon colors.
  2. Compute the ratio for each pair. You can use the GradientDeck check_contrast tool (gradientdeck.com/mcp), a browser devtools color picker, or an online calculator.
  3. Verify against the BUSIEST area of the background, not a sample. If the background is a gradient or image, test the lightest region the text might sit over.
  4. Check at the actual font size. 3:1 is fine for 24px+ text, but 4.5:1 is required below that.
  5. Test with your actual rendered output, not your design file. Glassmorphism, opacity, and blend modes change the effective contrast.

Accessible alternatives (that don't look terrible)

When a color pair fails, you don't have to switch to black-on-white. Here are the alternatives, in order of preference:

  • Darken the text color (not the background). Moving from #94a3b8 to #475569 usually fixes it without changing the design.
  • Lighten or darken the background by one step in your neutral scale.
  • Add a semi-transparent scrim behind the text (a 40% black overlay brings most pairs to AA).
  • Increase the font size to 24px+ regular or 18.66px+ bold, which only needs 3:1.
  • Switch to a different hue with higher contrast (e.g., from violet-400 to violet-600).

The GradientDeck MCP check_contrast tool takes a foreground and background color, computes the WCAG ratio, checks AA/AAA for normal and large text, and suggests accessible alternatives. Try it at gradientdeck.com/mcp.


Contrast isn't about compliance checkboxes — it's about whether your users can read your interface. Every failed pair is a user who can't see what you built. Test early, test often, and use the tools that make it fast.