Color contrast failures are the single most common accessibility issue on the web. The WebAIM Million study found them on 83.6% of home pages tested. Yet they're also one of the easiest issues to fix — once you understand the rules.
The WCAG Contrast Requirements
WCAG 2.2 defines two success criteria for color contrast:
1.4.3 Contrast (Minimum) — Level AA
- Normal text (< 18pt or < 14pt bold): minimum 4.5:1 contrast ratio
- Large text (≥ 18pt or ≥ 14pt bold): minimum 3:1 contrast ratio
- UI components and graphical objects: minimum 3:1
1.4.6 Contrast (Enhanced) — Level AAA
- Normal text: 7:1
- Large text: 4.5:1
The contrast ratio formula is (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter colour's relative luminance and L2 is the darker one's.
The Mathematics Behind Luminance
Relative luminance isn't just brightness — it accounts for how the human eye perceives different wavelengths:
function relativeLuminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(c => {
const sRGB = c / 255;
return sRGB <= 0.04045
? sRGB / 12.92
: Math.pow((sRGB + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
function contrastRatio(hex1, hex2) {
const lum1 = relativeLuminance(...hexToRgb(hex1));
const lum2 = relativeLuminance(...hexToRgb(hex2));
const [lighter, darker] = lum1 > lum2 ? [lum1, lum2] : [lum2, lum1];
return (lighter + 0.05) / (darker + 0.05);
}
Common Failures and Fixes
Grey text on white backgrounds
Light grey (#767676) on white is exactly 4.54:1 — barely passing. At #757575, it fails. Use #595959 or darker for safe normal text.
Blue links on coloured backgrounds
Many design systems use brand blue (#0066CC) as the link colour. On a dark navy background (#003366), that's only 2.6:1 — a clear failure.
Placeholder text
Placeholder text is treated as normal text for contrast purposes. The typical grey placeholder on a white input (#B0B0B0 on #FFFFFF) is 2.5:1 — fails badly.
/* ❌ Fails WCAG */
input::placeholder { color: #B0B0B0; }
/* ✅ Passes WCAG AA */
input::placeholder { color: #767676; }
Disabled states
Disabled controls are exempt from contrast requirements — but only if they're genuinely disabled (not just visually styled as disabled).
The Tricky Edge Cases
Gradients: WCAG requires the entire text background to meet contrast requirements. For text on a gradient, test at the lowest-contrast point.
Text over images: Image backgrounds change depending on the user's screen, browser rendering, and image loading. Safest approach: add a semi-transparent overlay or text shadow.
.hero-text {
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
/* OR */
background: rgba(0, 0, 0, 0.5);
padding: 0.5em 1em;
}
Focus indicators: WCAG 2.2 added Success Criterion 2.4.11 (Focus Appearance) — focus indicators must have at least 3:1 contrast against adjacent colours.
Automating Detection
Checking contrast manually across every combination of text and background in your app is impractical. Automated tools like AccessiScan scan your entire page across 201 WCAG 2.2 checks — including contrast — and generate a prioritised report with specific failing elements identified.
Start with Level AA compliance. The changes are usually small CSS tweaks with an outsized impact on usability for everyone, not just users with visual impairments.
Top comments (0)