UI/UX Atlas
Color Intermediate

Contrast & Accessibility: WCAG 2.x and APCA

Master the gap between passing a contrast ratio and actually being readable — covering WCAG 2.2 AA requirements, real-world limits, and how APCA reframes legibility.

9 min read

The full lesson

Accessible contrast directly affects two things: legal compliance and real readability. You can pass a contrast checker and still ship an interface that is genuinely hard to read. Understanding both the rules and their limits — when WCAG 2.x is enough, and when APCA reveals a problem it misses — is what separates box-checkers from designers who build for real people.

Why Contrast Gets Misunderstood

Most designers learn the 4.5:1 ratio, run a contrast checker, see a green checkmark, and move on. That answers the compliance question, but not the perceptual one.

The WCAG 2.x contrast formula was written in 1988. It works reasonably well for medium-sized text on white backgrounds. But it breaks down at large type, very small sizes, colored backgrounds, and other situations where the number diverges from how readable the text actually feels.

This is not a reason to ignore WCAG. The 2.x standard is the legal baseline in most jurisdictions — the EU’s EN 301 549, the US’s Section 508, and dozens of equivalent frameworks all reference it. Ignoring it creates real legal and ethical liability.

The right mental model: use WCAG 2.x as your compliance floor. Use APCA as a perceptual-quality check to catch the cases where a passing ratio still produces a poor experience.

WCAG 2.2 Contrast Requirements

WCAG 2.2 was published in October 2023 and is the current stable version as of 2026. Its contrast criteria are unchanged from WCAG 2.0 and 2.1.

Success Criterion 1.4.3 — Contrast (Minimum), Level AA:

  • Normal text (under 18pt / 14pt bold): minimum 4.5:1 contrast ratio
  • Large text (18pt or above / 14pt bold): minimum 3:1
  • Decorative text and logotypes: exempt

Success Criterion 1.4.6 — Contrast (Enhanced), Level AAA:

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

Success Criterion 1.4.11 — Non-text Contrast, Level AA:

  • UI components (button borders, form inputs, focus indicators) and informational graphics: minimum 3:1 against adjacent colors

What the Ratio Actually Measures

The WCAG contrast ratio uses relative luminance (L), calculated as:

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

L1 is the lighter color and L2 is the darker. Relative luminance comes from linearized sRGB values using a specific gamma correction curve.

Importantly, the formula is symmetric — it does not know which color is the text and which is the background. That detail matters when you compare it to APCA later.

Known Failure Modes

The WCAG 2.x formula produces counterintuitive results in several well-documented cases:

  • Very dark text on dark backgrounds: two near-black colors can fail 4.5:1 while being perfectly legible. Two dark colors with subtly different hues — dark navy on charcoal, for example — can pass while being nearly indistinguishable.
  • Saturated color pairs: certain mid-luminance chromatic pairs (medium blue on medium green, for instance) can pass 4.5:1 while creating chromatic vibration that makes text uncomfortable to read.
  • Large display type: a 72px headline at 3:1 is perfectly readable. The formula treats it the same as 10px body text at 3:1.
  • Thin vs. heavy weight: a 300-weight font at 4.5:1 reads worse than a 700-weight font at 3.5:1. The formula has no concept of stroke width.

APCA: The Advanced Perceptual Contrast Algorithm

APCA (Advanced Perceptual Contrast Algorithm) was developed by Andrew Somers as part of W3C research for WCAG 3.0. It addresses the failure modes above by modeling perceived lightness difference rather than raw luminance ratio.

Key Differences from WCAG 2.x

PropertyWCAG 2.xAPCA
OutputRatio (e.g., 4.5:1)Lc value (e.g., Lc 75)
DirectionalitySymmetric (ignores text/bg order)Asymmetric (polarity matters)
Font weight awareNoYes — lighter fonts need higher Lc
Font size awareTwo steps (normal/large)Continuous scale
Background luminanceNot modeledDarker backgrounds get different treatment
Adoption statusLegal standard (WCAG 2.x)Research/supplementary only

APCA’s Lc values range roughly from 0 to 106. Its usage guidelines recommend:

  • Lc 90+: maximum contrast; suitable for body text at any size
  • Lc 75: fluent body text at 14px and above, normal weight
  • Lc 60: subheadings; large bold text; placeholder text
  • Lc 45: non-content text; captions; disabled states
  • Lc 30: minimum for large bold decorative text
  • Lc 15 and below: not recommended for any text

Reading APCA Values in Practice

APCA is asymmetric. Dark text on a light background and light text on a dark background are not treated as equivalent, even if they produce the same absolute Lc value. Human visual perception treats near-black text on white differently from near-white text on black — even when the luminance difference is identical. In dark mode, you generally need a slightly higher Lc value than in light mode to achieve the same perceived readability.

APCA also ties minimum contrast requirements directly to font size and weight. A 400-weight 16px body font needs approximately Lc 75. A 700-weight 24px heading can get by with Lc 60. A thin decorative typeface at 12px might need Lc 90 or higher to be genuinely readable — a distinction WCAG 2.x does not capture.

Practical Workflows for Contrast Compliance

Tooling

For WCAG 2.x checking:

  • Figma: built-in contrast checker in the right-panel (shows ratio and AA/AAA pass/fail)
  • Polypane: live contrast overlay for WCAG 2.x and APCA simultaneously
  • Stark (Figma plugin): batch contrast audits across all text layers in a frame
  • axe DevTools: automated accessibility scan that flags WCAG 1.4.3 failures in the browser

For APCA:

  • APCA Contrast Calculator (git.apcacontrast.com): the reference implementation
  • Colour Contrast (Colour Oracle): desktop tool with APCA preview mode
  • Polypane: shows Lc alongside the WCAG ratio inline

Building a Compliant Token System

A semantic token architecture makes contrast compliance maintainable instead of manual. Rather than checking every text/background pairing individually, you design the token system so that any text token used on its paired surface token is guaranteed to meet 4.5:1.

A three-tier structure that enforces this looks like:

Primitive tokens
  gray-900: oklch(15% 0 0)
  gray-50:  oklch(97% 0 0)

Semantic tokens
  text-primary:    → gray-900   (on surface-default)
  surface-default: → gray-50

Component tokens
  button-label:    → text-primary
  button-surface:  → surface-default

When primitives are defined in OKLCH, you can generate tonal scales with mathematically predictable luminance steps. That makes it possible to verify that semantic pairings hit 4.5:1 programmatically — far more reliable than hand-picking hex values and hoping they happen to pass.

Do

  • Verify contrast at the token level, not just in Figma comps — a token that passes in light mode may fail when reused in dark mode.
  • Use OKLCH or LCH to build tonal scales: perceptually uniform lightness steps make it much easier to maintain contrast ratios across a palette.
  • Check non-text contrast for all interactive component boundaries — input borders, focus rings, and chart elements that convey meaning all fall under SC 1.4.11.
  • Include contrast compliance in your component library’s documented acceptance criteria so it is verified during code review, not just design review.
  • Use APCA to audit cases where WCAG 2.x gives a surprising pass — saturated mid-tone pairs and thin-weight body text are the two most common false positives.

Don't

  • Don’t use low-contrast text to visually de-emphasize secondary content — use size, weight, and spacing for hierarchy instead.
  • Don’t rely on hover states to restore contrast — text must meet minimum contrast in its default resting state.
  • Don’t assume a passing ratio on white carries over to colored card surfaces — re-check every distinct background in your system.
  • Don’t treat AAA (7:1) as unachievable gold-plating — for body text used by older adults or in bright outdoor environments, 7:1 is a meaningful target.
  • Don’t confuse APCA Lc 60 with the WCAG 2.x 3:1 large-text exemption — they are different models with different numeric scales; they are not interchangeable.

Contrast in Dark Mode

Dark mode introduces a class of contrast failures that light mode never surfaces. The two most common are overexposed reversed text and inherited light-mode semantic tokens.

Overexposed reversed text: a naive dark mode starts with pure #000000 background and #FFFFFF text. The luminance ratio is 21:1 — well above AAA. But at high brightness settings, this creates halation, a bloom effect around letterforms that reduces legibility. Near-black backgrounds like oklch(8% 0 0) or oklch(12% 0.01 250) solve this while still maintaining above-7:1 contrast.

Inherited light-mode semantic tokens: if your dark mode works by inverting hex values or swapping a CSS class, semantic tokens designed for a white surface get applied to a dark surface without re-verification. The text-secondary token that reads as “subtly de-emphasized” at 5:1 on white may be nearly invisible at 2.5:1 on a dark surface. Every semantic text/surface pairing needs independent contrast verification for each theme.

Luminance-step elevation — using progressively lighter surface colors (such as oklch 12%, 16%, 20%) to signal stacking layers in dark mode — also requires checking that text tokens maintain 4.5:1 against every surface step, not just the base.

Edge Cases Worth Knowing

Disabled States

WCAG explicitly exempts disabled UI components from contrast requirements. Presenting them at low contrast is a conventional affordance that signals non-interactivity. But this exemption is not a free pass to use any color you want. Users who cannot perceive color differences still need to understand why a control is unavailable. That means including a visible label, an accessible tooltip, or enough luminance contrast to see the component outline even if the fill is de-emphasized.

Placeholder Text

Placeholder text inside form inputs is body text and must meet 4.5:1. This is a frequent failure point because placeholder colors are often chosen to look “faded” — typically gray-400 or gray-300 on white, which falls below the threshold. Using visible top-aligned persistent labels eliminates this problem entirely. Labels remain visible after the user types and are easier to keep at full contrast.

Text Over Images and Gradients

The WCAG contrast criterion applies at every point where text overlaps a background — not just on average. Text over a photograph must be checked at the lightest region of the image beneath each character, not at a sampled midpoint. Reliable solutions include a semi-transparent scrim behind the text, a dark gradient overlay, or rendering the text over a contained solid-color field.

Communicating Contrast Decisions to Stakeholders

Accessibility requirements sometimes get framed as constraints and pushed back on. Reframe them as design precision: high-contrast text is not “bland” — it is readable in direct sunlight, on budget phone screens, and for the one in twelve men who has some form of color vision deficiency.

Pairing numeric evidence (the percentage of users affected) with a live demo under a brightness-reduced screen or a color-blindness simulation (Colour Oracle, Figma’s Accessibility plugin) is usually more persuasive than citing a standard number.

When a design decision requires a waiver or documented exception, record it. Note which criterion applies, which component is affected, what alternative meets the spirit of the requirement, and who signed off. This documentation matters in audits and protects the team from “we didn’t know” liability.