• WCAG
  • Color Contrast
  • Accessibility
  • AA

WCAG Color Contrast Guide: AA, AAA, and the Failures Designers Miss

Learn how WCAG contrast ratios work, when AA vs AAA applies, common UI pitfalls, and how to check pairs with a free browser tool.

Sherotree

·8 min read

Color contrast is one of the few accessibility rules you can measure with a calculator. That makes it tempting to treat as a one-click checkbox. In practice, most products still ship low-contrast body text, ghost buttons, and disabled styles that look “subtle” on a calibrated laptop and disappear on a sunny phone screen.

This guide covers what contrast actually means under WCAG 2.x, which thresholds apply where, the failure patterns that show up in real UI reviews, and a short workflow using Uwarp’s free colour contrast checker.

What “contrast ratio” means

WCAG contrast compares the relative luminance of two colors. Relative luminance is a value from 0 (black) to 1 (white) derived from sRGB. The ratio formula is:

Contrast ratio = (L1 + 0.05) / (L2 + 0.05)

  • L1 is the lighter color’s relative luminance
  • L2 is the darker color’s relative luminance

A pure black (#000000) on pure white (#FFFFFF) is 21:1. Same-color pairs are 1:1. Everything useful sits between those extremes.

You do not need to compute luminance by hand for daily work. You do need to know that the formula is about relative luminance, not about “how different the hex codes look” or “how saturated the brand feels.” Two bright pastels can look stylish and still fail badly.

AA vs AAA thresholds (quick table)

WCAG 2.x Success Criterion 1.4.3 (Contrast Minimum) defines AA. 1.4.6 (Contrast Enhanced) defines AAA. Level depends on whether text is normal or large.

LevelNormal textLarge text
AA≥ 4.5:1≥ 3:1
AAA≥ 7:1≥ 4.5:1

Large text in WCAG 2.x means approximately 18pt and larger, or 14pt and larger when bold. In CSS terms, that is often treated as about 24px regular or 18.66px bold, though exact rendering depends on the font metrics. When in doubt, treat borderline sizes as normal text and aim for 4.5:1.

What to aim for in product UI

  • Body copy, form labels, helper text, table cells: AA normal → 4.5:1 minimum. Prefer closer to 7:1 if the product is content-heavy or used outdoors.
  • Page titles and big section headers: AA large → 3:1 can pass, but many teams still keep titles near 4.5:1 so hierarchy stays readable when weight or size changes.
  • Icons that carry meaning, chart labels, badge text: treat like text at the size they render. A 12px badge is normal text, not large.
  • Non-text UI components (input borders, focus rings, selected states): covered by WCAG 2.1/2.2 non-text contrast (3:1 against adjacent colors) for many interactive controls. Do not assume text AA alone makes a control usable.

AAA is not “always better branding.” It is stricter. Some brand primaries cannot reach 7:1 on both light and dark surfaces without a second text color. That is a palette design problem, not a reason to ignore AA.

Common failure cases (beyond “the text is light gray”)

1. Body text that only fails on secondary surfaces

#6B7280 on #FFFFFF often lands near or just under 4.5:1 depending on exact gray. The same gray on #F9FAFB (a common page background) can drop further. Designers check the style against white in a style guide, then place it on tinted cards in production.

Fix pattern: Validate the actual foreground/background pair used in the layout, not the token against white alone.

2. Placeholder and “hint” text treated as decoration

Placeholders disappear when the user types, so teams often make them barely visible. Screen reader users and keyboard users still rely on visible labels. Low-contrast placeholders also fail for anyone with glare or mild low vision.

Fix pattern: Keep a persistent label at ≥4.5:1. If you keep placeholder text, do not rely on it as the only instruction, and do not push it below AA “because it’s temporary.”

White text at 70% opacity on a photo, or a brand teal link on a soft mint background, routinely fails. Opacity reduces effective contrast; sampling the flattened result matters more than the solid hex in the file.

Fix pattern: Sample the composite color (or place a solid scrim behind text), then re-check. Pair decorative backgrounds with a contrast pass using the same tool you use for solid UI.

4. Disabled controls that look “disabled” by failing contrast

A common anti-pattern: disabled buttons use the same brand fill at 40% opacity, and the label falls to ~2:1. Users cannot tell disabled from broken—or worse, cannot read why the action is unavailable.

WCAG does not require disabled text to meet the same contrast as active text in every interpretation, but usability still suffers. Prefer a clear disabled style (muted surface + icon/state) and keep helper text nearby at readable contrast explaining what unlocks the action.

5. Charts, tags, and status chips

Success green on white can pass for a large chip label and fail for a dense table cell. Red error text on a pale rose background often fails while looking “on brand.”

Fix pattern: Separate semantic hue from text color. Use a darker green/red for text than for soft surfaces.

6. Dark mode copy-paste

Inverting a light palette rarely preserves ratios. #9CA3AF on #111827 may look fine in a mock and still miss 4.5:1. Re-check every core text token in both themes.

Step-by-step: check a pair with Uwarp Colour Contrast Checker

Use this when you have two hex values—from Figma, CSS, or a brand deck.

  1. Open the free Colour Contrast Checker.
  2. Enter the foreground (text or icon) color and the background color.
  3. Read the ratio and the AA/AAA pass/fail for normal and large text.
  4. If you fail AA for body text, adjust one side:
    • Darken text on light surfaces, or
    • Lighten text on dark surfaces, or
    • Shift the surface (often easier than fighting a fixed brand primary).
  5. Re-test hover and pressed states as separate pairs if they change fill or text color.
  6. For brand-wide work, move from single pairs to a structured palette with Accessible Brand Colors, then spot-check critical pairs again in the contrast checker.

If you are browsing related utilities, the color tools category groups contrast and palette helpers in one place.

Invented pair for illustration:

  • Brand link: #3B82F6 on page background #EFF6FF
  • That combination often feels “airy” and can sit near or below 4.5:1 for small UI text.

A practical repair:

  • Keep the soft blue for surfaces (#EFF6FF / #DBEAFE)
  • Use a darker blue for text links (for example #1D4ED8 on white, then re-check)
  • Keep the bright blue for large buttons only if the label still clears AA on that fill

The brand stays recognizable; the role of each step in the scale changes.

Body text, UI chrome, and disabled states: a practical split

Think in three buckets when you review a screen:

  1. Reading text — paragraphs, lists, form labels. Hard floor: 4.5:1.
  2. UI chrome — borders, dividers, unselected tabs. Aim for enough separation that structure is obvious; interactive pieces often need 3:1 against neighbors.
  3. State text — error, success, warning messages. These are reading text. Soft tint backgrounds are not an excuse for soft tint copy.

Disabled and placeholder styles belong in a fourth conversation: communicate state without pretending low contrast is the only affordance.

Quick QA checklist (use before handoff)

  • Body and label tokens pass ≥4.5:1 on every surface they actually sit on (white, gray-50, cards, dark theme).
  • Large headings that rely on the 3:1 large-text rule are truly large/bold by WCAG size definition.
  • Text on images/gradients was checked after compositing (or sits on a solid scrim).
  • Links and buttons pass in resting, hover, and focus styles.
  • Error and success message text passes AA; soft alert backgrounds are separate from text color.
  • Icon-only controls have a visible affordance and sufficient contrast for the icon against its background.
  • Dark mode tokens were re-validated, not only inverted.
  • Critical pairs were checked in the colour contrast checker, not estimated by eye.

When contrast fights the brand

If marketing insists on a light primary on white, do not “win” by shipping 3:1 body text. Split roles:

  • Primary fill for large controls with white or near-white labels (verify the label pair).
  • Primary text as a darker sibling for inline links and small UI.
  • Primary soft for backgrounds and selected rows.

That split is exactly what accessible palette tools are for. Generate candidates with Accessible Brand Colors, then confirm the pairs you ship with the contrast checker.

Suggested next article

Once single pairs make sense, read Brand Primary → Accessible Palette: how to turn one seed color into text, surface, border, and semantic roles without guessing hex values.