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 Type | AA Minimum | AAA Enhanced |
|---|---|---|
| Normal text (< 18pt or < 14pt bold) | 4.5:1 | 7:1 |
| Large text (≥ 18pt or ≥ 14pt bold) | 3:1 | 4.5:1 |
| UI components and graphical objects | 3:1 | No requirement |
| Decorative text, logos | None | None |
| Inactive/disabled UI | None | None |
"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
| Foreground | Background | Ratio | AA Normal | AA Large |
|---|---|---|---|---|
| #000000 | #FFFFFF | 21:1 | Pass | Pass |
| #FFFFFF | #000000 | 21:1 | Pass | Pass |
| #FFFFFF | #0000FF | 8.6:1 | Pass | Pass |
| #FFFFFF | #FF0000 | 4.0:1 | Fail | Pass |
| #FFFFFF | #00CC00 | 1.4:1 | Fail | Fail |
| #000000 | #FFFF00 | 19.6:1 | Pass | Pass |
| #767676 | #FFFFFF | 4.5:1 | Borderline | Pass |
| #757575 | #FFFFFF | 4.6:1 | Pass | Pass |
| #FFFFFF | #4285F4 | 3.0:1 | Fail | Pass |
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:
- Open DevTools → Elements
- Click the color swatch next to
color - 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.