One predictable pain point with contrast-color() is that it only returns black and white named colors. From a design systems perspective, that’s not ideal because you want your colors. You want your harmonious brand and the colors you and your team spent thousands of man hours in meetings deciding on. Those colors.
In fact, an earlier version of Safari had color-contrast() (confusing I know, naming is hard) which allowed you to pass in a list of best candidates to choose from. I beleive that proposal got mired in standards discussions, color contrast algorithms, and competing proposals; and contrast-color() is what survived which got simplified down to a binary result.
In the future though, we can use contrast-color() and if() together to help pick the value we want. Alas, at the time of writing no browser supports both if() and contrast-color(). But we can use Lea Verou’s --contrast-color() workaround to experiment with how this will work in Chromium today.
See the Pen contrast-color() powered design system colors by Dave Rupert (@davatron5000) on CodePen.
The CSS to get this working looks something like this:
@property --captured-color {
syntax: "<color>";
inherits: true;
initial-value: white;
}
/* https://lea.verou.me/blog/2024/contrast-color/ */
@function --contrast-color(--bg-color) {
--l: clamp(0, (l / var(--l-threshold, 0.623) - 1) * -infinity, 1);
result: oklch(from var(--bg) var(--l) 0 0);
}
:root {
--ds-text-white: wheat;
--ds-text-black: darkslategray;
}
button {
background-color: var(--ds-button-bg);
--captured-color: --contrast-color(var(--ds-button-bg));
color: if(
style(--captured-color: oklch(1 0 0)): var(--ds-text-white);
else: var(--ds-text-black);
);
}
And that’s it! Using (a form of) contrast-color() you can select the proper tokens from your design system. Cool.
One quirky bit needed to make it work is defining a type for --captured-color using CSS @property, a trick I learned from Roma Komarov. To be honest I don’t fully understand the why behind Registered Custom Properties and the Computed Value Time Behavior superpower, but my simple brain created a rule “If you’re going to compare a variable to a <color> in a CSS if() statement, make sure to register the variable as a <color>.”
If/when any of the browsers start supporting both features, I expect we’ll have to update oklch(1 0 0) in the style query to white or rgb(255 255 255). At least, I hope it works that way.
One unexpected challenge that I encountered with this demo was that if I abstracted out the if statement out into its own custom function, it would break. I’m not 100% sure why but I think it’s based on how CSS functions cache results. I’ll have to dig into this more, but I’m happy the inline if statement works. Hopefully someone smarter can figure it out.
Anyways, exiting times. And can we pause for a moment and marvel at how this is all vanilla 2026 CSS?! What a world.