Tutorials

Color Contrast Algorithms: WCAG 2 vs APCA

9 min read

When a designer says "this text has enough contrast," they usually mean a number: a ratio like 4.5:1 calculated by some algorithm. But not all contrast algorithms are created equal. The one embedded in WCAG 2 has been the industry standard since 2008, but it has documented limitations. The APCA — Advanced Perceptual Contrast Algorithm — was developed as a modern replacement and is central to the draft WCAG 3.0 specification.

Understanding how these algorithms work, where they agree, and where they diverge is essential for any designer or developer who cares about accessibility. This tutorial walks through both from first principles.

WCAG 2 Relative Luminance Formula

The WCAG 2 contrast algorithm calculates the ratio between the relative luminance of two colors. Relative luminance is a measure of the amount of light a color reflects, normalized so that absolute white is 1.0 and absolute black is 0.0.

Step 1: Linearize the sRGB Values

sRGB color values are gamma-encoded — they are not proportional to physical light. Before computing luminance, you must linearize each channel.

For each channel value (R, G, or B) expressed as a decimal from 0 to 1:

if channel <= 0.04045:
    linear = channel / 12.92
else:
    linear = ((channel + 0.055) / 1.055) ^ 2.4

Step 2: Compute Relative Luminance

Apply the CIE luminance coefficients, which reflect the eye's different sensitivity to red, green, and blue light:

L = 0.2126 × R_linear + 0.7152 × G_linear + 0.0722 × B_linear

The weighting tells you something important: green contributes 71.5% of perceived luminance, red about 21.3%, and blue a mere 7.2%. A vivid blue #0000FF has a relative luminance of only 0.072, even though it looks "bright" to casual observation.

Step 3: Calculate the Contrast Ratio

Given the relative luminance L1 of the lighter color and L2 of the darker color:

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

The 0.05 offset accounts for ambient light reflected from a screen (veiling glare). It ensures the ratio never divides by zero (pure black has L=0 but gets a luminance of 0.05 for the denominator).

A Worked Example

For white (#FFFFFF) on a dark slate (#1E293B):

  1. White: L = 1.0 (all channels fully linear)
  2. #1E293B in RGB: (30, 41, 59) → normalized: (0.118, 0.161, 0.231) → linearized and luminance-weighted: L ≈ 0.021
  3. Contrast ratio: (1.0 + 0.05) / (0.021 + 0.05) = 1.05 / 0.071 ≈ 14.8:1

A ratio of 14.8:1 comfortably passes all WCAG levels. Check any pair instantly using the Contrast Checker.

WCAG 2 Thresholds

Criterion Text Size Required Ratio
AA (normal text) < 18pt regular, < 14pt bold 4.5:1
AA (large text) ≥ 18pt regular, ≥ 14pt bold 3:1
AA (UI components) Icons, form borders 3:1
AAA (normal text) Any 7:1
AAA (large text) Any 4.5:1

Known Limitations of WCAG 2

The WCAG 2 formula has several well-documented problems that affect real-world usability:

1. Light text on dark backgrounds is overly penalized. The formula is symmetric: it does not care which color is foreground and which is background. But human vision is not symmetric. We generally read dark text on light backgrounds more easily than light text on dark backgrounds, especially at small sizes. WCAG 2 cannot distinguish between these cases.

2. Very dark or very light text can fail even with high luminance contrast. A near-black #0A0A0A on pure black #000000 has a very low ratio (about 1.1:1), which makes sense. But WCAG 2 also struggles with certain mid-tone combinations that the human eye can read perfectly well.

3. Font weight and size are only coarsely addressed. WCAG 2 has two buckets: "normal" and "large" text, with bold treated as a modifier for the 14pt threshold. In reality, contrast readability is a smooth function of font size, weight, and rendering quality. A 700-weight 16px font at 4.5:1 reads very differently from a 300-weight 12px font at 4.5:1.

4. The formula was built for desktop monitors of the early 2000s. Modern OLED screens, HDR displays, and varying ambient lighting conditions are not accounted for.

APCA: Advanced Perceptual Contrast Algorithm

The APCA was developed by Andrew Somers under the Accessibility Guidelines Working Group as the contrast engine for WCAG 3.0. It takes a fundamentally different approach: instead of measuring a luminance ratio, it computes a lightness difference calibrated to human visual perception of text.

Core Differences from WCAG 2

Aspect WCAG 2 APCA
Metric Luminance ratio Lightness difference (Lc value)
Directional? No (symmetric) Yes — text/background order matters
Font weight/size Two coarse buckets Continuous lookup table
Score type Ratio (e.g., 4.5:1) Signed Lc value (e.g., Lc 75)
Minimum passing Ratio ≥ 4.5

How APCA Works

Step 1: Linearize (similar to WCAG 2)

APCA uses a slightly modified linearization, raising values to the power of 2.4 (same exponent as sRGB):

Y = (sRGB / 255) ^ 2.4  (simplified)

Step 2: Compute Luminance

APCA uses the same CIE coefficients as WCAG 2:

Y = 0.2126 × R + 0.7152 × G + 0.0722 × B

Step 3: Apply the APCA Soft Clamp

APCA applies a "soft clamp" to low luminances to simulate how our eyes adapt in low-light conditions:

if Y <= 0.022:
    Y_adjusted = Y + (0.022 - Y) ^ 1.414

Step 4: Calculate the SAPC (Spatial Average Polarity Contrast)

APCA uses a signed difference that distinguishes between dark-text-on-light (positive Lc) and light-text-on-dark (negative Lc):

if Y_background > Y_text:
    # Dark text on light background (polarity: +)
    Lc = (Y_background ^ 0.56 - Y_text ^ 0.57) × 1.14
else:
    # Light text on dark background (polarity: −)
    Lc = (Y_background ^ 0.65 - Y_text ^ 0.62) × 1.14

The different exponents (0.56 vs 0.57 for text, 0.65 vs 0.62 for background) encode the empirical asymmetry between reading dark-on-light vs. light-on-dark.

Reading APCA Lc Values

The output is a signed number. Positive Lc means dark text on a light background; negative Lc means light text on a dark background. For usability assessment, take the absolute value:

| |Lc| Value | Usability | |-----------|-----------| | < 30 | Text is effectively invisible | | 30–45 | Only suitable for large, bold decorative text | | 45–60 | Minimum for large (18pt+) body text | | 60–75 | Acceptable for most body text (14pt+ at normal weight) | | 75–90 | Good contrast for standard body text (12pt regular) | | 90+ | Excellent contrast; suitable for very small text |

A Worked Example

White #FFFFFF text on Tailwind's blue-500 #3B82F6:

  • WCAG 2 ratio: approximately 3.0:1 → Fails AA for normal text
  • APCA Lc: approximately Lc 57 → Acceptable only for large text (18pt+)

Both algorithms agree this combination is problematic for body text. But APCA gives more nuance: at Lc 57, it is usable for 18pt headings but not for 14pt body copy, whereas WCAG 2 gives a binary pass/fail.

Now compare #1D4ED8 (blue-700) on white #FFFFFF:

  • WCAG 2 ratio: approximately 7.2:1 → Passes AAA
  • APCA Lc: approximately Lc 79 → Good for 12pt+ regular weight text

Side-by-Side Comparison

Where They Agree

For most straightforward cases — dark text on white, or black text on a pastel background — WCAG 2 and APCA produce concordant verdicts. A pair with a 4.5:1 WCAG ratio typically corresponds to an Lc value between 55 and 70, depending on direction.

Where They Diverge

Case 1: Light text on dark backgrounds

WCAG 2 treats white-on-dark identically to black-on-light with the same ratio. APCA applies different exponents for each direction. Empirically, APCA is more lenient with light-on-dark at high Lc values — a combination that WCAG 2 might require 7:1 for, APCA may rate as Lc 80 and consider acceptable for standard body text.

Case 2: Small, light-weight text

A ratio of 4.5:1 in WCAG 2 is supposed to cover all normal text. But APCA, with its font-lookup table, would flag a thin-weight (300) 12px font at the same 4.5:1 pair as needing Lc 90+ — meaning it fails APCA's criteria even though it passes WCAG 2.

Case 3: Large decorative type

A 48px bold heading can be read at much lower contrast than body copy. WCAG 2's "large text" threshold of 3:1 is a blunt instrument. APCA's sliding scale based on font size and weight is more precise: a 700-weight 32px heading might pass at Lc 45, while a 300-weight 32px heading would need Lc 60.

Example Pairs: WCAG 2 vs APCA Verdict

Foreground Background WCAG 2 Ratio APCA Lc WCAG 2 AA APCA (16px/400wt)
#FFFFFF #2563EB 4.6:1 Lc 67 Pass Pass
#FFFFFF #3B82F6 3.0:1 Lc 57 Fail Fail
#374151 #F9FAFB 12.6:1 Lc 92 Pass Pass
#6B7280 #FFFFFF 5.9:1 Lc 63 Pass Pass (marginal)
#9CA3AF #FFFFFF 2.8:1 Lc 49 Fail Fail
#000000 #F59E0B 12.5:1 Lc 90 Pass Pass

The verdicts align in most practical cases. Divergences show up at the margins and are most significant when font size and weight are factored in.

The Future of Contrast in WCAG 3.0

WCAG 3.0 (currently a working draft, not yet final) replaces the ratio-based contrast model with APCA-based criteria called Bronze/Silver/Gold conformance levels. The key changes:

  1. Font-aware requirements: Minimum Lc values depend on font size and weight. There is no single number; instead, a lookup table maps (size, weight, usage) to a minimum Lc.
  2. Directional contrast: Light-on-dark and dark-on-light are evaluated differently.
  3. Non-text elements: Different requirements for icons, UI components, and decorative graphics.
  4. Smooth scale: The Binary pass/fail of WCAG 2 is replaced by a more granular conformance model.

WCAG 3.0 has been in draft since 2021 and is not expected to become a finalized recommendation until at least 2026–2027. Legal requirements (ADA, EN 301 549, AODA) currently reference WCAG 2.1 AA or 2.2 AA, so those remain the binding standard for compliance purposes.

Which to Use Today

For compliance and legal defensibility: use WCAG 2.1 AA.

If you need to demonstrate compliance with accessibility laws, pass automated audits, or satisfy a government procurement requirement, WCAG 2.1 (or 2.2) AA is the right standard. Every auditor, every automated tool, and every lawyer understands 4.5:1.

For better design outcomes: use APCA as a supplement.

APCA gives you more actionable information. If you have a combination that passes WCAG 2 at 4.5:1 but uses a thin-weight font at 12px, APCA will flag it as marginal. Use APCA to catch cases where WCAG 2 gives a false pass.

Practical workflow: 1. Check WCAG 2 ratio for compliance using the Contrast Checker 2. If a combination is borderline (4.5:1–5.5:1), also consider the font size and weight 3. Use APCA Lc as an additional signal — if it is below 60 for body text, reconsider even if WCAG 2 passes 4. For creative projects and design systems not subject to strict compliance, APCA is the better design tool

Key Takeaways

  • WCAG 2 calculates a luminance ratio using linearized sRGB values and the CIE coefficients (0.2126 R + 0.7152 G + 0.0722 B). The ratio must be ≥ 4.5:1 for normal text (AA) and ≥ 7:1 for enhanced contrast (AAA).
  • WCAG 2's limitations include: symmetric treatment of dark-on-light vs. light-on-dark, coarse font size buckets, and calibration to early-2000s display technology.
  • APCA computes a signed Lc value that distinguishes polarity (dark text / light background vs. light text / dark background) and uses different exponents for each direction.
  • APCA's font lookup table makes contrast requirements depend on font size and weight — a more realistic model of how text readability actually works.
  • Most real-world pairs produce concordant verdicts between the two algorithms. Divergences are most significant at small font sizes, low font weights, and light-on-dark configurations.
  • WCAG 3.0 will adopt APCA as its contrast engine, but it remains a draft and current legal requirements still reference WCAG 2.1/2.2.
  • Best practice today: use WCAG 2 for compliance, APCA as a supplemental design quality check. Use the Contrast Checker to verify WCAG 2 ratios for every significant text/background pair in your design.

Related Colors

Related Brands

Related Tools