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.