How to theme with CSS variables (custom properties)


Some time back we wrote an article about theming with Sass, which tackled the tricky front-end task of creating CSS themes without the need to do battle with specificity. It’s a really neat technique which makes use of Sass maps and a little bit of BEM.

Three years on, the CSS tricks and tools we have at our disposal have advanced hugely and one of them, in particular, is really useful for solving this exact issue. Given that the Sass theming article has become one of our most popular blogs, I thought we ought to update it. So what’s this new CSS feature?

Enter CSS variables

You might also have heard these referred to as CSS custom properties. Much like the name suggests, they allow you to store references to values and make use of them down the line:

First, we store a value:

:root { --green-bright: #27efa1; }

… then we make use of it:

background-color: var(--green-bright);

At this point, CSS variables don’t look much different to variables in Sass, but there’s one really neat difference – CSS variable values can be reassigned.

With this knowledge, we can do some really interesting stuff. For example, let’s say you wanted to theme an individual component – I’ll use the example of a generic section for housing content.

First, we set some variables:

:root { --sectionBG: var(--white); --sectionText: var(--grey-dark); --linkColor: var(--green-bright); --titleColor: var(--green-bright); }

Then scaffold out our default section styling making use of these variables:

section { background-color: var(--sectionBG); color: var(--sectionText); padding: 7rem 0; h2 { color: var(--titleColor); } a { color: var(--linkColor); } .btn { &:hover { background-color: var(--linkColor); border-color: var(--linkColor); color: var(--sectionText); } } }

Now when we want to create an alternate theme we can use a BEM modifier class to reassign these variables:

.section--grey { --sectionBG: var(--grey-dark); --sectionText: var(--grey-light); --titleColor: var(--white); }

So with almost no additional CSS we’ve created a completely new theme for our section. Cool!

Extrapolating CSS variables further

With this trick in mind, it’s easy to imagine how we might extend the idea further – we might, for example, create a bunch of generic variables such as --theme-primary and --theme-accent and, with some careful planning and use of the cascade, we’d be able to re-theme entire applications just by reassigning them.

You could also create different themes on a page-by-page basis by injecting a style tag into the head which reassigns these variables on different pages – we do something like this on the Browser London website. This is particularly nice because it means you don’t have to predefine themes ahead of time – they can be user-generated and stored in a database (again, something we use on our site).

One of my favourite things about this approach is that it embraces some of CSS’s core features rather than fighting against them. Doing so is not only good for reducing code size and complexity, but it also encourages consistency in UI because components feel much more related to each other rather than being isolated blocks. Another useful advantage is that, because CSS variables are native to the browser, you can experiment with them on the fly in dev tools. I find this really useful for testing which colour from my palette is going to work best in a particular scenario.

Example of theming with CSS Variables

Things to consider

As you can probably tell I’m a big fan of this technique, but as with all modern CSS tricks, there are things to consider. Chief amongst them is a front-end dev’s good friend – browser support.

At the time of writing just over 87.5% of browsers globally support CSS variables, which is really pretty good, but it still leaves 12.5% unaccounted for (including, as you might expect, all versions of Internet Explorer). Given we’re likely to be using them for fundamental parts of our application rather than progressive enhancement we need a good way of dealing with this.

At the moment I’m using PostCSS with the postcss-custom-properties plugin in my build process, which basically adds a new CSS property above your variable usage with the compiled value of that variable.

Css variables colour exampleThis seems like a fairly good solution but still isn’t ideal, and I’ve definitely had cases where I’ve reassigned a variable multiple times and the plugin has compiled it down to the wrong value. In practice, this has resulted in things like a button being a different colour in IE to other browsers. It’s up to you to decide how important things like this will be for your product, and there’s definitely a counter-argument that perhaps I’ve been making things too complicated in order for this to happen in the first place.

Snippet of CSS variables codeThese hiccups aside I think this is a fantastic way to embrace modern CSS and simplify our styling process. Whilst this article was limited to theming you can find so many more use cases for custom properties, and hopefully you’ll enjoy the flexibility they afford you.