Reference11 min read

WCAG 2.1 vs APCA

What 4.5:1 actually means, where the 0.05 offset comes from, why APCA exists, why dark-on-light reads better at the same ratio, and why Tailwind's `text-lg` doesn't qualify as large text under WCAG. Plus the color-blindness modes that hide failures contrast checkers miss.

A designer drops a color pair into a contrast checker and gets back 4.51 — Pass AA. They ship it. Three weeks later a user with low vision says the text is unreadable. Both things are true. WCAG 2.1 is a useful floor, not a complete model of legibility — and "large text" doesn't mean what most designers think it means. This guide walks the math behind 4.5:1, why APCA exists, the color-blindness modes that hide failures, and the gap between "passes the spec" and "is actually readable."

The Formula That Picks the Number

WCAG 2.1 contrast ratio is:

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

where L1 is the relative luminance of the lighter color, L2 is the darker, and luminance comes from the sRGB linearization:

L = 0.2126 × R_lin + 0.7152 × G_lin + 0.0722 × B_lin

The 0.05 offset isn't arbitrary — it's the assumed reflectance of a "black" pixel on a typical display. Without the offset, pure black on pure black would produce a divide-by-zero. With it, even the worst case bottoms out at a finite, well-defined ratio of 1:1.

The result is a number between 1.0 (identical colors) and 21.0 (pure black on pure white).

The WCAG Contrast Checker runs this calculation client-side, plus the alternative APCA model, plus four color-blindness simulations — paste two colors and the verdict ladder is instant.

The Verdict Ladder

Use case WCAG 2.1 AA WCAG 2.1 AAA
Normal text 4.5:1 7:1
Large text 3:1 4.5:1
UI components, graphical objects 3:1
Logos exempt exempt
Incidental text (decorative, inactive) exempt exempt

Three things to notice. First, the gap between AA and AAA is meaningful — AAA is what most accessibility audits actually require for public-sector and healthcare work, but most product teams stop at AA. Second, UI components (focus rings, form-control borders, icons that convey state) only need 3:1, which is why a teal focus ring on a white background can pass even when it looks subtle. Third, the logo and incidental exemptions are narrow — your wordmark is exempt, but your tagline, your nav links, and your captions are not.

The "Large Text" Trap

WCAG defines large text as 18pt regular or 14pt bold. Translated to CSS pixels at the standard 16px = 12pt mapping:

  • 18pt regular ≈ 24px
  • 14pt bold ≈ 18.66px

Tailwind's text-lg is 18px regular. That's not large text under WCAG. Neither is text-xl (20px regular). A designer who picks a 4.0:1 ratio thinking they're safe for text-lg is failing AA for normal text by half a point.

The cleanest fix is to design at the normal-text threshold (4.5:1) by default and only treat anything larger as a bonus. This avoids the entire "is this large enough?" debate and survives later font-size changes.

Why APCA Exists

WCAG 2.1's formula is symmetric: dark-on-light and light-on-dark with the same luminance pair produce the same ratio. Human perception isn't symmetric. Dark text on a light background is genuinely easier to read at the same nominal ratio because the visual system adapts asymmetrically — pupils constrict in bright environments, anti-aliasing renders thin strokes differently against light versus dark, and the perceived blur of a stroke is direction-dependent.

APCA (Accessible Perceptual Contrast Algorithm) is the WCAG 3.0 candidate that models this:

  • Polarity-aware coefficients. Different multipliers for "black on white" (BoW) versus "white on black" (WoB) directions.
  • Soft-clip at very dark colors. Below a threshold (luminance 0.022), the formula transitions smoothly instead of behaving like the linear region, which prevents over-credit for near-black colors.
  • Score scale. Output is Lc — a perceptual lightness contrast number, typically -108 to +106. Roughly: |Lc| ≥ 75 for body text, ≥ 60 for headlines, ≥ 45 for large text. The sign tells you the polarity.

A color pair that passes 4.5:1 WCAG with light text on a dark background may fail the APCA body-text threshold; the same pair flipped (dark text, light background) might pass both. Until WCAG 3.0 is finalized, APCA is informational — design to WCAG 2.1, sanity-check with APCA.

Color-Blindness Modes

Contrast and differentiation are not the same thing. Two colors can hit 4.5:1 against each other but become indistinguishable under common color-vision deficiencies:

  • Protanopia (~1% of men) — red cones non-functional; red and green collapse toward yellow/gray
  • Deuteranopia (~1% of men) — green cones non-functional; similar collapse to protanopia
  • Tritanopia (rare, <0.01%) — blue cones non-functional; blue and yellow collapse
  • Achromatopsia (very rare) — no color vision; brightness only

The WCAG Contrast Checker runs Brettel/Viénot/Mollon daltonization to show the same color pair as it would appear under each deficiency. A red-on-green status indicator with adequate contrast on a standard display can become a single yellow-on-yellow blob under deuteranopia. The fix is not "use more contrast" — it's "don't encode state in color alone." Add an icon, a label, a position.

Common Failure Patterns (And Fixes)

Teal-on-white at brand color. #006673 on white = 9.04:1 — passes AAA for normal text. Most teal/cyan brand colors are fine on white. The same #006673 on #0a0a0a = 1.84:1 — fails everything. Dark mode needs its own color, not the same color flipped.

Gray captions on white. #9ca3af on #ffffff = 2.85:1 — fails AA for both normal and large text. The instinctively-readable gray for captions is #6b7280 or darker (4.69:1).

Brand-color buttons with white text. A pure mid-blue #3b82f6 button with white text = 3.13:1 — passes AA Large but fails AA Normal. Button labels are normal text. Either darken the button or use a heavier weight (which doesn't change the ratio, but the spec for AA Large kicks in at 14pt bold).

Focus ring against background. Focus rings only need 3:1 against the adjacent color, not against the page background. A subtle teal ring is fine as long as it's 3:1 against the button color, not against the page.

The Color Palette and CSS Gradient tools both ship with built-in contrast checks for the same reason — the failure usually happens at palette-design time, not at code-review time.

The Honest Limitations

WCAG contrast is a useful, conservative model — but it's a model. The things it cannot measure:

  • Spatial frequency. Thin strokes are harder to read than thick strokes at the same ratio. WCAG ignores stroke weight (except for the bold-text large-text exemption).
  • Anti-aliasing rendering. ClearType, subpixel rendering, and modern font hinting all shift effective contrast in ways the formula doesn't capture.
  • User-installed fonts. Web fonts that fail to load fall back to system fonts with different stroke widths; the contrast ratio is unchanged but legibility may not be.
  • Ambient light and screen calibration. A 4.5:1 pair on a calibrated office monitor is not 4.5:1 on a phone in direct sunlight or on a TN-panel laptop tilted away from the viewer.
  • Logo and incidental exemptions are narrow. "It's a logo" doesn't cover wordmarks-in-running-prose, brand names in nav, or stylized callouts.

Use the checker to catch the easy mistakes. Test with real users — including users on small screens, in bright environments, and using assistive tech — to catch the rest.

Related Tools

TL;DR

WCAG 2.1 says 4.5:1 for normal text, 7:1 for AAA, 3:1 for large text (18pt regular or 14pt bold) and UI components. Tailwind text-lg is not large text. APCA is the perceptual successor that knows dark-on-light beats light-on-dark at the same ratio. Color blindness can hide failures that contrast misses — never encode state in color alone. The WCAG Contrast Checker runs all of it at once and shows the nearest passing color when you fail.

Try the tools