Today I learned! Jeremy Keith wrote about an interesting detail about CSS custom properties, also known as CSS variables, that he learned from Lea Verou: They don’t support the Cascade when a value is invalid. Or, as Lea writes in her article Hybrid positioning with CSS variables and max():
The browser doesn’t know if your property value is valid until the variable is resolved, and by then it has already processed the cascade and has thrown away any potential fallbacks.
Jeremy goes on to explain that this is the reason why using the color()
function will fall back successfully if you use it like this:
/* CSS */
p {
background-color: red;
background-color: color(display-p3 1 0 0);
}
But it will fail if you use a custom property like so:
/* CSS */
:root {
--myvariable: color(display-p3 1 0 0);
}
p {
background-color: red;
background-color: var(--myvariable);
}
What happens is that the browser first figures out the cascade and then looks at the custom properties. If a property is invalid “at computed-value time”, there is no fallback value because the browser has already thrown away the other values. So in the example above, the browser will not display the red background-color, but instead unset
it.
A lot of people are switching from Sass to using CSS custom properties these days. And unless you still have to support IE11, you can totally do that because browser support is solid. So it will only be a matter of time until using CSS variables will have be the new normal. Knowing that they fail all that gracefully is an important thing to know, though. Especially if you are using them to use features that are only supported by a few browsers, like the new color functions color()
, lab()
, or lch()
.
But what can you do? Do we have to wait and skip using custom properties for values that might fail? Not really. As I wrote in my recent post about the new color functions coming to CSS, you could use supports()
to check for support before setting the properties:
/* CSS */
:root {
--myvariable: red;
}
@supports (color: color(display-p3 1 0 0)) {
:root {
--myvariable: color(display-p3 1 0 0);
}
}
p {
background-color: var(--myvariable);
}
Another option would be to use a media-query like color-gamut
. But if you want to set many values at once in one ruleset, supports()
might be the better solution. Sara Soueidan recently wrote about how she defines Global and Component Style Settings with CSS Variables . In such a case, you could rely on a feature query using supports()
to set all the custom properties for supporting browsers.
/* CSS */
:root {
/* UI Colors */
--color--primary: rgb(217,0,189);
--color--secondary: rgb(242,199,0);
}
@supports (color: lch(50% 132 334)) {
:root {
/* UI Colors for browsers that support LCH colors */
--color--primary: lch(50% 132 334);
--color--secondary: lch(82% 132 86);
}
}
-
This is the 16th post of my 100 days of writing series. You can find a list of all posts here.
~