02
Contrast
That #9E9E9E caption looks fine to you. It fails for roughly 8% of men with color vision deficiencies, and quietly for everyone else.
10 min read
Gray text on a white background is the default aesthetic of modern UI. It signals hierarchy: this is secondary, this is metadata, this is less important. The problem is that most grays are less important and less readable.
Compare
Light mode caption at #9E9E9E on #ffffff, 2.85:1. Looks clean in the mockup. Fails AA.
Light mode caption at #525252 on #ffffff, 7.5:1. Still passes AA. No strain.
WCAG AA requires 4.5:1 contrast ratio for body text. That's not bureaucracy. It's the threshold where text remains readable for users with low vision, on poor displays, in bright sunlight, and for everyone who is tired.
Contrast
Secondary text, captions, and metadata often drift toward gray. Drag the slider and watch when readability breaks.
Drag the slider toward white. There is a precise moment where the text stops being "stylish gray" and becomes "unreadable gray." You crossed 4.5:1 without noticing if you weren't watching the number.
Contrast Ratio
Sample text on your chosen background.
WCAG contrast formula
(Llight + 0.05) / (Ldark + 0.05)
L = 0.2126 × R + 0.7152 × G + 0.0722 × B
R, G, B are linearized sRGB channels (0-1), not raw 0-255 values.
Ltext = 0.3419 · Lbg = 1.0000
(1.0000 + 0.05) / (0.3419 + 0.05) = 2.7:1
Below the WCAG AA minimum of 4.5:1.
The calculator defaults to the light-background example above, regardless of site theme.
Enter your own text and background colors with the swatches or hex fields. The calculator applies the WCAG formula: relative luminance for each color, then (Llight + 0.05) / (Ldark + 0.05). Try #9E9E9E on white and watch it land around 2.85:1. Then try #ffffff on #000000 and watch it hit 21:1, a perfect score that can still fail real readers.
The #9E9E9E caption is everywhere: timestamps, helper text, disabled-looking labels that aren't disabled. It averages about 2.85:1 on white. It fails AA. It ships anyway because it looks clean on a Retina display in a dim room.
The Rule
Body and secondary text need at least 4.5:1 contrast against their background.
Low contrast doesn't just exclude users with color vision deficiencies. About 8% of men have some form of it. It increases cognitive load for everyone. Gray text is slower text.
On light backgrounds, secondary text still needs to pass AA without drifting to #9E9E9E. The fixed caption in the comparison above uses #525252 at about 7.5:1 on white:
.caption, .muted, .secondary {
color: #525252;
}.caption, .muted, .secondary {
color: #525252;
}When Contrast Is Too High
WCAG sets a floor, not a ceiling. There is no maximum contrast requirement in WCAG 2.x. A calculator will happily give you 21:1 for pure white on pure black and call it accessible. That number is technically correct and practically wrong for a large share of your audience.
Halation is the main failure mode. Light text on a near-black background can look haloed or blurred, especially at night, on OLED screens, and for users with astigmatism. The W3C's own contrast research notes that maximum contrast does not serve the broadest audience: older eyes struggle with scatter and glare, and excessive contrast increases fatigue. FAA Human Factors design standards cite 15:1 as a reasonable maximum for body text, well below the 21:1 of black on white.
This hits dark mode hardest. The same ratio feels harsher when text is lighter than its background. Pure #ffffff on #000000 is 21:1. Pure white on #121212 is still about 18.7:1. Both pass WCAG. Both can trigger halation. The fix is not less accessibility. It is softer extremes: off-white text around #e8e8e8 on dark gray around #121212, landing near 12-15:1. Still AAA. Much less glare.
Compare
Dark mode body at #ffffff on #000000, 21:1. Passes every checker. Blurs for many readers.
Dark mode body at #e8e8e8 on #121212, about 15:1. Still strong. Much less halation.
On light backgrounds, near-black text on white is less prone to halation. #171717 on white at roughly 18:1 is fine for body copy. The danger zone is mostly light-on-dark at the extremes. The dark theme shipped as inverted light theme without retuning colors.
The Rule
For dark backgrounds, target roughly 7:1 to 15:1 with off-white text on dark gray, not pure white on pure black. Reserve maximum contrast for users who opt in via prefers-contrast: more.
Dark backgrounds need a separate caption token. Reusing #525252 on #121212 drops to about 2.4:1. Fading body text with opacity is just as unreliable. The effective ratio shifts with every background you stack it on.
Softened dark mode body copy targets roughly 12-15:1. On this site, headings and body use #e8e8e8 on #181818 at about 14.5:1. Secondary and caption tokens keep the same hierarchy as light mode at #969696 and #757575:
@media (prefers-color-scheme: dark) {
body {
color: #e8e8e8;
background: #181818;
}
.secondary {
color: #969696;
}
.caption, .muted {
color: #757575;
}
}@media (prefers-color-scheme: dark) {
body {
color: #e8e8e8;
background: #181818;
}
.secondary {
color: #969696;
}
.caption, .muted {
color: #757575;
}
}This site toggles dark mode with a data-theme attribute (via next-themes), not only prefers-color-scheme. The media query above is still the pattern most teams ship; the values match this site's dark palette.
The upcoming APCA contrast model (the direction for WCAG 3) treats light-on-dark and dark-on-light as different problems and experiments with maximum contrast caps for dark themes. Until then, treat your calculator result as a range: below 4.5:1 fails, above 15:1 on dark backgrounds deserves a second look, and above 17:1 light-on-dark is halation territory for body text.
Common Mistake
Treating contrast as "higher is always better." Low gray text fails most users quietly. Maximum light-on-dark contrast fails a different group loudly, and no automated checker catches it.