Skip to content
Free Tool Arena

How-To & Life · Guide · Developer Utilities

How to Check Color Contrast

WCAG 4.5:1 AA / 7:1 AAA thresholds, large-text leniency, how luminance is calculated, link colors, and dark-mode.

Updated April 2026 · 6 min read

Color contrast is the single most-violated accessibility rule on the web, and it’s also one of the easiest to fix. WCAG defines numeric ratios between foreground and background luminance; fail them and people with low vision, poor displays, or sunlit screens cannot read your text. The good news is the math is fully automatable and the thresholds are well-defined. This guide explains what the ratios mean, the difference between AA and AAA, where large-text leniency applies, how luminance is actually calculated, and the subtler cases — link colors, disabled states, dark mode — that trip up teams who check the hero text once and call it a day.

Advertisement

The WCAG thresholds

Contrast is expressed as a ratio between 1:1 (no contrast) and 21:1 (pure white on pure black). The thresholds you need to memorize:

                     Normal text    Large text   UI components
WCAG AA              4.5 : 1        3 : 1        3 : 1
WCAG AAA             7 : 1          4.5 : 1      n/a

Normal text is anything under 18pt (roughly 24px) regular or 14pt bold. Large text is 18pt+ regular or 14pt+ bold. UI components include form borders, icons, focus indicators — anything non-text that carries meaning.

AA vs AAA

AA is the legal baseline in most jurisdictions (ADA, Section 508, EN 301 549, AODA). If you’re shipping a public product, aim for AA at minimum.

AAA is the gold standard. It’s required only for government services in a few places. For product work, hitting AAA on body copy is a reasonable north star — it costs you nothing and gives you headroom for bad displays, direct sunlight, and aging users.

How luminance is calculated

Contrast ratio comes from relative luminance, not raw RGB. Each channel is normalized to 0–1, gamma-decoded, then weighted by how sensitive the human eye is to that color:

L = 0.2126 R + 0.7152 G + 0.0722 B

Green dominates. A pure green (#00ff00) has luminance 0.7152; pure red is 0.2126; pure blue is 0.0722. That’s why dark blue on black looks horrible (both have tiny luminance) while dark red on black reads at least a little.

contrast ratio = (L_lighter + 0.05) / (L_darker + 0.05)

The + 0.05 prevents division by zero and accounts for flare. The practical upshot: you can’t intuit contrast from “how different the colors look.” Run the numbers.

Large-text leniency

Bigger text is easier to read, so WCAG lets you drop to 3:1 for large text. This is what lets you use medium-gray headlines that would fail on body copy. The cutoffs:

18pt regular  =  24px           (AA large allowed)
14pt bold     =  18.66px bold    (AA large allowed)

A common mistake: using the leniency for 18px bold subheads. 18px bold is not 14pt bold — it’s ~13.5pt. Measure in pt, not px, to stay honest.

Link colors inside paragraphs

Links need two contrasts: against the page background and against the surrounding body text. WCAG 1.4.1 says color alone can’t be the only signal. Two ways to pass:

Underline the link. Now the underline carries the signal and contrast of the link color only needs to meet the text-vs-background ratio.

Use a 3:1 color difference between the link and the body text in addition to a passing contrast against the background.

If you’re stripping underlines for aesthetics, you need the 3:1-to-text test, plus another signal (bold, hover underline) to cover non-color signaling.

Disabled states

WCAG explicitly exempts disabled controls from the contrast requirements — but that doesn’t mean “invisible is fine.” Users still need to see there’s a button, they just need to understand it’s not interactive. A common pattern: keep the text contrast at ~3:1 against the button, and lighten the background fill. Reduced opacity works visually; just don’t drop below ~2:1 or the control becomes impossible to locate.

Placeholder text

Most browsers ship with placeholder text at ~40% opacity, which almost always fails AA. Override it:

::placeholder {
  color: #6b7280; /* gray-500, ~4.6:1 on white */
  opacity: 1;
}

And remember placeholders should never replace labels. Even at passing contrast they disappear the moment the user starts typing.

Dark mode

Pure white text on pure black is 21:1 — technically perfect but actually painful on OLED screens. Soften both ends:

bg:   #0a0a0a   (off-black)
text: #e5e5e5   (off-white)
ratio: 14.5 : 1   (plenty, easier on the eyes)

Check your dark-mode palette separately. Teams often nail light mode and then invert colors without re-checking contrast for every component. Border colors, disabled states, and brand colors usually need bespoke dark-mode values.

Images with text overlays

Hero images with headlines are where contrast fails most dramatically because the background is not a flat color. Options:

Dark overlay. A 40–60% black gradient over the image pushes average luminance down enough for white text to pass.

Text shadow. A subtle text-shadow: 0 1px 3px rgba(0,0,0,0.6) adds local contrast without touching the image.

Contained background. Put the text on a semi-opaque panel rather than directly on the image.

Brand colors that fail

A surprising number of brand palettes fail AA at their default weights. Medium greens (#4caf50-ish) on white are usually 2.2:1. Sky blues (#3b82f6-ish) land around 3.7:1 — fine for large text or a button background, but not for 14px body copy. When you inherit a failing brand color:

Darken the shade for body use and keep the original for illustrations and icons. Most design systems ship both — call them blue-500 and blue-700 — and prescribe which is for text.

Automated vs manual checks

Automated tools catch the arithmetic failures — flat color on flat color — but miss:

  • Text over images or gradients
  • Content that depends on runtime theme state
  • Dynamically generated colors (user avatars, charts)
  • Focus indicators that only appear on keyboard nav

Run axe or Lighthouse as a baseline; then manually spot-check your top ten pages in both light and dark mode, with focus visible.

Common mistakes

Checking only the primary text color. Secondary, tertiary, disabled, and placeholder colors all need their own pass.

Testing once on a beautiful monitor. Low-end mobile screens, projectors, and sunlight effectively reduce contrast. AA leaves headroom for that — don’t spend it all in the design review.

Using gray-on-gray for “subtle” UI. Subtle shouldn’t mean unreadable. If your timestamp copy fails AA, raise its contrast or accept that it will be skipped entirely.

Relying on color to convey state. Red text for “error” and green for “success” should always pair with an icon or label.

Forgetting focus indicators. Focus rings need 3:1 against the adjacent colors. The default browser ring usually passes; custom rings often don’t.

Picking gradients as text backgrounds. The worst-case spot in the gradient is what you need to test, not the average.

Run the numbers

Feed foreground and background values into the contrast checker for an instant AA / AAA verdict. When you need to massage a color into the passing range without losing the brand, the color converter lets you nudge hex, HSL, and OKLCH side by side, and the color picker pulls exact values out of existing assets so you start from the real source, not a guess.

Advertisement

Found this useful?Email