DEV Community

Snappy Tools
Snappy Tools

Posted on

WCAG Color Contrast: Why Your UI Might Be Failing Accessibility Tests

You've probably seen accessibility audits flag contrast ratio failures. Maybe Lighthouse gives your site a poor score, or a design review marks text as "unreadable for low-vision users." But what does a contrast ratio actually measure, and what do the thresholds mean?

Here's the practical explanation — with the math kept simple.

What contrast ratio means

The WCAG contrast ratio compares the relative luminance of two colours: foreground (text) and background. Luminance is a measure of perceived brightness, not just the raw RGB value.

The formula:

contrast ratio = (L1 + 0.05) / (L2 + 0.05)
Enter fullscreen mode Exit fullscreen mode

Where L1 is the lighter of the two luminance values and L2 is the darker. The result is a ratio from 1:1 (no contrast — identical colours) to 21:1 (maximum contrast — pure black on pure white).

Relative luminance for an sRGB colour (r, g, b):

For each channel, first linearise:
  if channel <= 0.04045: linear = channel / 12.92
  else: linear = ((channel + 0.055) / 1.055) ^ 2.4

L = 0.2126 × R_linear + 0.7152 × G_linear + 0.0722 × B_linear
Enter fullscreen mode Exit fullscreen mode

The linearisation step is what trips people up. Raw hex values are not luminance values. #808080 (mid-grey) has a luminance of approximately 0.216, not 0.5 — the human eye perceives brightness nonlinearly.

The WCAG thresholds

WCAG 2.1 defines two conformance levels that affect colour contrast:

Level AA (minimum standard, required for most legal compliance):

  • Normal text (< 18pt, or < 14pt bold): 4.5:1
  • Large text (≥ 18pt, or ≥ 14pt bold): 3:1
  • UI components and graphical objects: 3:1

Level AAA (enhanced accessibility):

  • Normal text: 7:1
  • Large text: 4.5:1

These thresholds account for users with moderately low vision who don't use assistive technology. Level AA is the target for most production web applications.

Common failures in practice

Placeholder text on inputs

Browsers default to placeholder text at about 40% opacity. Many designers replicate this with color: #999 on a white background, which gives roughly 2.8:1 — well below AA. Fix: use #767676 (color: #767676) for the minimum AA-compliant grey on white.

Light grey text on white

The industry default "muted" colour — #aaa or #999 — fails AA on white backgrounds. A 4.5:1 compliant grey on #fff is at least #767676. Check your card subtitles, helper text, and disabled states.

Coloured buttons

Blue primary buttons often fail when the text is white. #1a73e8 (Google Blue) gives approximately 3.0:1 with white text — passing for large text only. If your button text is small (< 18pt), you need a darker blue.

Brand colour on background

Green (#2f855a — a popular Tailwind green) on white: ~5.1:1. Passes AA. But on a light grey background (#f5f6fa) it drops to ~4.6:1 — still passing, but close. If you lighten the background to #f0f0f0, it fails.

Status badges and tags

Warning yellow is a frequent offender. #fbbf24 on #fff gives roughly 1.7:1. Use #d97706 (amber-600) for the text colour and a light yellow background instead of the reverse.

The icon-only problem

Decorative icons don't need to meet contrast requirements. But informational icons — icons that convey meaning without accompanying text — do. The threshold is 3:1 against the adjacent background.

If you have an error icon that's #ef4444 on #fff, the contrast is about 4.0:1. Passes for an icon (3:1 threshold). But if the same red is used for small text, it fails (needs 4.5:1). Colour alone shouldn't convey information in any case — pair icons with visible labels where possible.

Testing contrast quickly

The fastest approach is a browser DevTools check: inspect an element, go to colour picker, and look for the WCAG AA/AAA badges (Chrome and Firefox both show these inline now).

For a more systematic check — comparing specific hex values, testing a background/text combination across your design system — a standalone tool is faster. I use snappytools.app/color-contrast-checker for this: paste two hex values (or pick with a colour picker), get the ratio, WCAG AA/AAA badge, and an immediate pass/fail. No signup, runs in the browser.

Fixing failures

If text fails:

  1. Darken the text colour — don't lighten the background
  2. Increase font size (large text threshold is 3:1, not 4.5:1)
  3. Use bold weight for borderline cases (14pt+ bold qualifies as "large text")

If a UI component fails:

  1. Add a visible border or outline (the contrast applies to the boundary of the component)
  2. Darken the icon or indicator

If your brand colour fails:

  1. Use the brand colour as an accent — not as the primary text colour on a light background
  2. Create a darker variant for text contexts

WCAG 3.0 and APCA

WCAG 3.0 will replace the current luminance-based ratio with APCA (Advanced Perceptual Contrast Algorithm), which accounts for text size, font weight, and polarity (light-on-dark vs dark-on-light) more accurately. The current 4.5:1 standard can fail in some edge cases (very light backgrounds with mid-grey text) and pass in others where the visual result is borderline.

APCA isn't required yet — WCAG 2.1 is still the legal baseline. But if you're designing a new system, it's worth testing against both.


The takeaway: contrast ratio failures are usually fixable in minutes once you know which elements to check. The three most common culprits — placeholder text, muted body copy, and primary brand colour buttons — cover 80% of the issues in most UI codebases.

Top comments (0)