wcagaccessibilitycontrast

WCAG Contrast Ratio Requirements Explained

· Cosyslabs

WCAG 2.1 requires a minimum 4.5:1 contrast ratio for normal text and 3:1 for large text at Level AA. Level AAA requires 7:1 for normal text and 4.5:1 for large text. Contrast ratio is calculated from the relative luminance of two colors — it measures perceived brightness difference, not color difference.

The Requirements at a Glance

Content TypeAA MinimumAAA Enhanced
Normal text (< 18pt or < 14pt bold)4.5:17:1
Large text (≥ 18pt or ≥ 14pt bold)3:14.5:1
UI components and graphical objects3:1No requirement
Decorative text, logosNoneNone
Inactive/disabled UINoneNone

"Large text" in CSS terms: at least 24px regular or 18.67px bold (equivalent to 18pt and 14pt in print).

How Contrast Ratio Is Calculated

WCAG uses relative luminance — a value from 0 (pure black) to 1 (pure white) that represents perceived brightness.

Step 1: Convert to Relative Luminance

For each RGB channel (0–255), normalize to 0–1, then apply gamma correction:

function toLinear(channel) {
  const c = channel / 255;
  return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}

function relativeLuminance(r, g, b) {
  const rLin = toLinear(r);
  const gLin = toLinear(g);
  const bLin = toLinear(b);
  // ITU-R BT.709 coefficients
  return 0.2126 * rLin + 0.7152 * gLin + 0.0722 * bLin;
}

Step 2: Calculate the Contrast Ratio

function contrastRatio(lum1, lum2) {
  const lighter = Math.max(lum1, lum2);
  const darker = Math.min(lum1, lum2);
  return (lighter + 0.05) / (darker + 0.05);
}

The + 0.05 offset prevents division by zero for pure black and adjusts for real-world viewing conditions.

Example Calculation

// White (#FFFFFF) vs Black (#000000)
const whiteLum = relativeLuminance(255, 255, 255); // 1.0
const blackLum = relativeLuminance(0, 0, 0);       // 0.0

const ratio = contrastRatio(whiteLum, blackLum);
// (1.0 + 0.05) / (0.0 + 0.05) = 1.05 / 0.05 = 21:1  (maximum possible)

// #767676 (medium gray) on white
const grayLum = relativeLuminance(118, 118, 118); // ~0.2
const ratio2 = contrastRatio(1.0, 0.2);
// (1.0 + 0.05) / (0.2 + 0.05) = 1.05 / 0.25 = 4.2:1  (fails AA for normal text!)

// #757575 (one shade darker) on white
const grayLum2 = relativeLuminance(117, 117, 117); // ~0.195
// Ratio: 4.48:1  (passes AA for normal text)

Common Color Combinations and Their Ratios

ForegroundBackgroundRatioAA NormalAA Large
#000000#FFFFFF21:1PassPass
#FFFFFF#00000021:1PassPass
#FFFFFF#0000FF8.6:1PassPass
#FFFFFF#FF00004.0:1FailPass
#FFFFFF#00CC001.4:1FailFail
#000000#FFFF0019.6:1PassPass
#767676#FFFFFF4.5:1BorderlinePass
#757575#FFFFFF4.6:1PassPass
#FFFFFF#4285F43.0:1FailPass

Key insight: Green on white often fails because green has high luminance. Blue on white depends strongly on the shade.

Common Failure Patterns

Light gray text on white background

This is the most common failure — designers prefer subtle text, but low contrast fails WCAG:

/* Fails AA: ratio ≈ 3.9:1 */
.caption {
  color: #888888;
  background: #ffffff;
}

/* Passes AA: ratio ≈ 4.6:1 */
.caption {
  color: #767676;  /* Actually #757575 or darker */
  background: #ffffff;
}

Placeholder text in inputs

Placeholder text is often styled with very low contrast:

/* Fails AA: typical browser default */
input::placeholder {
  color: #aaaaaa; /* ratio ~2.3:1 on white — fails badly */
}

/* Passes AA */
input::placeholder {
  color: #767676; /* ratio ~4.6:1 */
}

Colored buttons

Vibrant button colors can fail with white text:

/* Fails — red on white: 4.0:1 */
.btn-danger {
  background: #ff0000;
  color: white;
}

/* Passes — darker red */
.btn-danger {
  background: #c0392b;
  color: white; /* ratio ~5.1:1 */
}

Focus indicators

WCAG 2.2 added SC 2.4.11 (AA): focus indicators must have a minimum area and contrast. The focus ring must contrast at least 3:1 against adjacent colors.

/* Good focus indicator */
:focus-visible {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
}

Testing for Contrast

In Browser DevTools

Chrome DevTools shows contrast ratio when you inspect a text element:

  1. Open DevTools → Elements
  2. Click the color swatch next to color
  3. The color picker shows the contrast ratio with the computed background

Automated Testing

// Jest + axe-core
import { axe, toHaveNoViolations } from "jest-axe";

test("navigation has no accessibility violations", async () => {
  const { container } = render(<Navigation />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});
# Lighthouse CLI
npx lighthouse https://yoursite.com --only-categories=accessibility

Manual Spot Checks

  • Color Contrast Checker Tool — test any two colors instantly
  • Simulate color blindness in Chrome DevTools → Rendering → Emulate vision deficiencies
  • Use a grayscale screenshot — contrast problems become obvious without color cues

WCAG 3 and APCA

WCAG 3 (in development) will replace the current contrast algorithm with APCA (Advanced Perceptual Contrast Algorithm). APCA:

  • Accounts for text size and weight more accurately
  • Considers that dark text on light background and light text on dark background need different ratios
  • Uses a non-linear scale (Lc values, not ratios)

APCA is not yet the official standard, but you can experiment with it now for forward compatibility.

Building an Accessible Palette

// Find the minimum lightness in OKLCH that passes 4.5:1 on white
function minLightnessForAA(hue, chroma) {
  // Binary search for the darkest text that still passes AA
  let low = 0, high = 1;
  while (high - low > 0.001) {
    const mid = (low + high) / 2;
    const color = oklchToRgb(mid, chroma, hue);
    const ratio = contrastRatio(
      relativeLuminance(...color),
      1.0 // white background
    );
    if (ratio >= 4.5) {
      high = mid;
    } else {
      low = mid;
    }
  }
  return high;
}

The key principle: define your accessible text colors first (against your background), then derive brand colors that pass contrast requirements — not the other way around.

Try It Now

Check any foreground/background color pair with the Color Contrast Checker — get the ratio, AA/AAA pass/fail status, and suggested accessible alternatives.

More Tools from Cosyslabs

  • PDF Convert All — Export accessible documents as PDFs. WCAG contrast requirements apply to PDFs under PDF/UA (ISO 14289) — the same ratios used in web accessibility apply to digital documents.
  • Rough Estimator — Estimate the design effort for an accessibility audit and remediation pass across an existing product.
  • Astrilio — A beautifully designed app by Cosyslabs — built with an accessible color palette that meets WCAG AA throughout.
  • Cosyslabs — The studio behind Dev Tools !, Unit Convert All, Routine Toolkit, CastFleet, and more.