CSS contrast-color(): Automatic Black or White Text
The CSS contrast-color() function picks black or white text for any background color, so text on brand-colored buttons and tags stays readable without hard-coding it.
Syntax
.button {
background: var(--brand);
color: contrast-color(var(--brand)); /* white or black */
}
The function takes one color and returns either white or black, whichever contrasts more with it. That's all it does today: you can't pass a list of candidate colors or a target ratio.
Browser support (October 2026)
- Safari 26 shipped it first (2025).
- Firefox 146.
- Chrome and Edge 147 (April 2026), which made it Baseline "newly available".
Older browsers will ignore the declaration, so provide a fallback first:
.tag {
background: var(--tag-bg);
color: #1A1A1A; /* fallback */
color: contrast-color(var(--tag-bg));
}
What happened to color-contrast()?
Earlier drafts of CSS Color Level 6 described a more powerful color-contrast() function with a vs keyword, a list of candidates and a target such as to AA (for example color-contrast(wheat vs tan, sienna, #d2691e to AA)). Safari shipped it only behind a flag. The CSS Working Group replaced it with the simpler contrast-color(), so don't use the old syntax in production code.
Limits to keep in mind
- The pick may surprise you. Under the WCAG 2 formula one of black or white always reaches at least about 4.58:1, but for mid-tones the winner is often black where designers expect white: on #808080, black text gets 5.32:1 while white gets only 3.94:1. If that clashes with your brand, adjust the background color instead. Check real pairs in the contrast checker.
- The exact algorithm may change. The spec leaves room for future contrast algorithms beyond the WCAG 2 formula, so treat the result as a sensible default, not a compliance guarantee.
- It doesn't help with images or gradients behind text; you still need an overlay or a solid background.
Doing the same thing in JavaScript
function luminance(hex) {
const [r, g, b] = [1, 3, 5].map(i => {
const c = parseInt(hex.slice(i, i + 2), 16) / 255;
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function bestText(bgHex) {
const L = luminance(bgHex);
const withWhite = 1.05 / (L + 0.05);
const withBlack = (L + 0.05) / 0.05;
return withWhite >= withBlack ? '#FFFFFF' : '#000000';
}
Related: WCAG contrast requirements, CSS color variables, and the color accessibility checklist.