CSS has a feature called Custom Properties. You know this.
html {
--brandColor: red;
}
.card {
border-color: var(--brandColor);
h2 {
color: var(--brandColor);
}
}
People also — somewhat interchangeably — refer to these as CSS variables. Somehow, that doesn’t bother me, even though I tend to be a stickler about naming things. For instance, there is no such thing as a frontend developer. There are front-end developers who focus on the front end.
But here, the names feel like they make sense despite me not exactly nailing down how I like to see them being used. Like, Custom Property feels right. When I create --something
, that’s used as a property in CSS but I just made up the name myself. It’s a… custom… property. And then I use it later with the var()
function, which obviously stands for “variable”, because now this custom properties value has turned into a dynamic variable. So calling what is happening here a “CSS variable” seems entirely fine.
OK moving on I guess we need to talk about CSS variables for an entire issue.
Just the other week I was trying to see if there was a clean way to ask CSS what the value of cos(25deg)
was. I feel like I got so close, trying both setting the value to a property that takes a unitless number and typing the variable first, but I couldn’t quite get it. There is a lesson here about never giving up, as Bramus proved by giving it a fresh college try and proving it absolutely can be done.
You totally do need to type variables sometimes, the 99% use case is allowing them to be animated which the browser can (mostly) on do if it knows the type. You could also consider it a form of “type safety” so hardcore TypeScript nerds will probably like it.
Above is about as niche of a situation as you can get.
What are CSS variables actually useful for?
I like thinking of the most common use case for things. The most common use case for grid is to put two things side by side. The most common use case for a <dialog>
is an important confirm/cancel question. The most common use case for a popover
is a footnote. The most common use case for SVG is a logo. For CSS variables, it’s a brand color.
Even on a fairly simple website, I’d bet there is one particular important color that you end up having to set a number of times. Using a variable for it just keeps things DRY and allows you to tweak it easily. That’s exactly the code I started this post out with. But variables are more and more powerful. Just that --brandColor
is tweakable without changing it…
footer {
background: color-mix(in oklch, var(--brandColor), black 20%);
}
Now if we tweak that brand color, we’re tweaking the tweaks (man).
Even wilder to me is that setting one custom property (see how effortless I can switch the term back and forth?) can have effects all over the place. This is thanks to container style queries.
Consider code like this:
@container style(--darkBackground) {
> * { color: white; }
}
Now if --darkBackground
is set (to any value at all) on any element, all direct children of it have white text. Support for this is pretty limited, so it’ll be a while until any of us have any strong understanding of how this will affect how we write CSS. To me, this is similar to :has()
in how a change anywhere on the page can affect changes elsewhere in (I’d argue) unexpected ways. Powerful ways. Maybe useful ways. But unexpected ways. CSS used to be a pretty darn top-down language and that’s changing.
How about a feature with such limited support there… isn’t any. One of those things is the if()
statement (woah), which is only useful for testing CSS variables. I’m a fan, but let’s not get all into that here. Another Lea Verou post is Proposal: CSS Variable Groups. Check it:
:root {
--color-green: {
100: oklch(95% 13% 135);
200: oklch(95% 15% 135);
/* ... */
900: oklch(25% 20% 135);
};
}
This turns into variables that are used like var(--color-green-200)
. Such a chill way to declare a set of variables without so much repetition. It’s just for authors, but that’s fine I think. CSS variables I feel like exist mostly in a post-pre-processing era (heh), so even though we’d probably abstract this with a loop or whatever in the past, we don’t have that anymore, so need syntactical help.