Color & Contrast for Readable Text
WCAG 2.2 contrast ratios, why pure black on white creates harshness, near-black recipes, and the specific pitfalls of dark mode contrast inversion.
Color & Contrast for Readable Text
The Legibility Equation
Text legibility depends on the contrast between the text color and its background. This sounds obvious, but the specifics are less intuitive: the relationship between perceived contrast and measured contrast is not linear, and some of the most common "safe" choices — black text on white background, white text on dark background — can produce optical problems of their own.
This article covers the standards, the measurements, and the practical choices.
WCAG 2.2 Contrast Requirements
WCAG (Web Content Accessibility Guidelines) version 2.2 defines minimum contrast ratios for text, measured on a scale from 1:1 (identical colors) to 21:1 (pure black on pure white).
The requirements:
- Normal text (below 18pt / 14pt bold): minimum 4.5:1 contrast ratio
- Large text (18pt or larger / 14pt bold or larger): minimum 3:1 contrast ratio
- UI components and graphics: minimum 3:1 contrast ratio against adjacent colors
- Decorative text and logos: no requirement
These are minimum requirements for AA compliance. WCAG 2.2 AAA level requires 7:1 for normal text and 4.5:1 for large text — a meaningful uplift that most production sites do not achieve throughout, but should aim for in high-readability contexts (long-form articles, legal text, medical information).
Check contrast ratios programmatically using tools like the Paciello Group's Colour Contrast Analyser, or through browser developer tools (Chrome DevTools > Accessibility panel shows contrast ratios inline).
/* Verify these ratios in your color system */
:root {
/* Body text — must achieve ≥ 4.5:1 against background */
--color-text: #1a1a1a; /* ~17.1:1 on white */
--color-text-muted: #6b7280; /* ~4.6:1 on white — AA passing, barely */
/* Large text / UI — must achieve ≥ 3:1 */
--color-text-subtle: #9ca3af; /* ~2.9:1 on white — FAILS for normal text, passes for large */
/* Never use for text */
--color-decorative: #d1d5db; /* ~1.7:1 — decorative use only */
}
Why Pure Black on White Is Harsh
Pure black (#000000) on pure white (#FFFFFF) achieves a contrast ratio of 21:1 — the theoretical maximum. By WCAG standards, this is not a problem. Perceptually, it can be.
The issue is halation — a visual effect where very high-contrast borders cause a slight "glow" or "bleed" at the edge of text. Some readers (particularly those with dyslexia or visual processing differences) find pure black text on pure white more difficult to read than text set at slightly lower contrast, because the letters appear to vibrate or swim. The effect is compounded on screens with bright backlights.
The standard practice in professional typography: use a near-black (approximately #1a1a1a to #333333) and an off-white (approximately #f8f5f0 to #fafafa) for body text, maintaining high contrast without the harshness of absolute black on absolute white.
/* Near-black body text on off-white — professional standard */
:root {
--color-text-body: #1c1c1e; /* iOS-style near-black */
--color-bg: #fafaf8; /* Warm off-white */
/* Contrast ratio: approximately 17.9:1 — well above 4.5:1 */
}
/* Or, warm paper tone for editorial */
:root {
--color-text-body: #1a1814; /* Very warm near-black */
--color-bg: #f5f0e8; /* Warm paper tone */
/* Contrast ratio: approximately 12:1 */
}
Building a Text Color Hierarchy
A complete text color system needs enough levels to express hierarchy without introducing accessibility violations:
:root {
/* Primary — headings, body text */
--text-primary: #111827; /* ~19:1 on white */
/* Secondary — captions, metadata, secondary info */
--text-secondary: #374151; /* ~11:1 on white */
/* Tertiary — placeholder text, disabled states */
--text-tertiary: #6b7280; /* ~4.6:1 on white — AA minimum for normal text */
/* Quaternary — hint text, decorative — LARGE TEXT ONLY */
--text-quaternary: #9ca3af; /* ~2.9:1 — only AA-compliant for text ≥ 18pt */
/* Interactive */
--text-link: #2563eb; /* ~4.7:1 on white */
--text-link-hover: #1d4ed8; /* ~6.2:1 on white */
}
Dark Mode: The Inversion Pitfall
Dark mode typography is not the inverse of light mode. Simply swapping your light text on dark background for dark text on light background produces visual problems:
Halation in reverse: White text on a very dark background can produce even stronger halation effects than black on white. The eye's point spread function causes bright text on dark backgrounds to appear slightly blurred at the edges. Using a warm off-white (#e8e6e1 or similar) rather than pure white (#ffffff) for body text on dark backgrounds reduces this.
Perceived brightness is not linear: A mid-gray that achieves 4.5:1 in light mode does not translate to the same perceived contrast in dark mode. Test contrast ratios explicitly in both modes.
Reduced contrast in dark mode can aid readability: For body text on dark backgrounds, a contrast ratio of 7:1 to 12:1 is often more comfortable than the maximum 21:1. Pure #ffffff on pure #000000 is the dark-mode equivalent of the pure black/white halation problem.
/* Dark mode text colors */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f1117; /* Deep but not pure black */
--text-primary: #e8e6e1; /* Warm off-white — ~13:1 on background */
--text-secondary: #a0a8b0; /* ~5.2:1 on background */
--text-tertiary: #6b7280; /* ~3.1:1 — large text only in dark mode */
}
}
Key Takeaways
- WCAG 2.2 requires 4.5:1 contrast for normal text and 3:1 for large text (AA level)
- Pure black (#000000) on pure white (#FFFFFF) achieves 21:1 but causes halation; near-black on off-white is the professional standard
- Secondary text at #6b7280 is the approximate floor for AA compliance — go lighter only for large text
- Dark mode requires explicit contrast testing; the light mode palette cannot simply be inverted
- Off-white body text (#e8e6e1) on dark backgrounds reduces halation compared to pure white