Before the advent of CSS custom properties (we might call them “variables” in this article as that’s the spirit of them), implementing multiple color schemes on the same website usually meant writing separate stylesheets. Definitely not the most maintainable thing in the world. Nowadays, though, we can define variables in a single stylesheet and let CSS do the magic.
Even if you aren’t offering something like user-generated or user-chosen color themes, you might still use the concept of theming on your website. For example, it is fairly common to use different colors themes across different areas of the site.
We’re going to build out an example like this:
In this example, all that changes between sections is the color hue; the variations in lightness are always the same. Here’s an example of a simplified color palette for a specific hue:
A palette of multiple hues might look something like this:
This would take effort to do with RGB color value, but in HSL only one value changes.
Enter custom properties
Custom properties have been around for a while and are widely supported. Polyfills and other solutions for IE 11 are also available.
The syntax is very similar to traditional CSS. Here is an overview of the basic usage:
It’s common to see variables defined on the :root
pseudo-element, which is always <html>
in HTML, but with higher specificity. That said, variables can be defined on any element which is useful for scoping specific variables to specific elements. For example, here are variables defined on data attributes:
Adding calc() to the mix
Variables don’t have to be fixed values. We can leverage the power of the calc()
function to automatically calculate values for us while adhering to a uniform pattern:
Since CSS doesn’t support loops, a preprocessor would be handy to generate a part of the code. But remember: CSS variables are not the same as Sass variables.
Implementing CSS variables
What we’re basically trying to do is change the color of the same component on different sections of the same page. Like this:
We have three sections in tabs with their own IDs: #food
, #lifestyle
, and #travel
. Each section corresponds to a different hue. The data-theme-attribute
on the div.wrapper
element defines which hue is currently in use.
When #travel
is the active tab, we’re using the --first-hue
variable, which has a value of 180°. That is what gets used as the --hue
value on the section, resulting in a teal color:
<div class="wrapper" data-theme="travel">
.wrapper[data-theme="travel"] {
--hue: var(--first-hue); /* = 180° = teal */
}
Clicking any of the tabs updates the data-theme attribute to the ID of the section, while removing the hash (#
) from it. This takes a smidge of JavaScript. That’s one of the (many) nice things about CSS: they can be accessed and manipulated with JavaScript. This is a far cry from preprocessor variables, which compile into values at the build stage and are no longer accessible in the DOM.
<li><a href="#food">Food</a></li>
const wrapper = document.querySelector('.wrapper');
document.querySelector("nav").addEventListener('click', e => {
// Get theme name from URL and ditch the hash
wrapper.dataset.theme = e.target.getAttribute('href').substr(1);
})
Progressive enhancement
When we use JavaScript, we should be mindful of scenarios where a user may have disabled it. Otherwise, our scripts — and our UI by extension — are inaccessible. This snippet ensures that the site content is still accessible, even in those situations:
// progressive enhancement:
// without JavaScript all sections are displayed, the theme is only set when the page loads
wrapper.dataset.theme = wrapper.querySelector('section').id;
This merely allows the tabs to scroll up the page to the corresponding section. Sure, theming is gone, but providing content is much more important.
While I chose to go with a single-page approach, it’s also possible to serve the sections as separate pages and set [data-theme]
on the server side.
Another approach
So far, we’ve assumed that color values change linearly and are thus subject to a mathematical approach. But even in situations where this is only partially true, we may still be able to benefit from the same concept. For instance, if lightness follows a pattern but hue doesn’t, we could split up the stylesheet like this:
<head>
<style>
:root {
--hue: 260;
}
</style>
<link rel="stylesheet" href="stylesheet-with-calculations-based-on-any-hue.css">
</head>
Supporting web components
Web components are an exciting (and evolving) concept. It’s enticing to think we can have encapsulated components that can be reused anywhere and theme them on a case-by-case basis. One component with many contexts!
We can use CSS variable theming with web components. It requires us to use a host-context()
pseudo-selector. (Thanks to habemuscode for pointing this out to me!)
:host-context(body[data-theme="color-1"]) {
--shade-1: var(--outsideHSL);
}
In summary…
Theming a website with CSS custom properties is much easier than the workaround approaches we’ve resorted to in the past. It’s more maintainable (one stylesheet), performant (less code), and opens up new possibilities (using JavaScript). Not to mention, CSS custom properties become even more powerful when they’re used with HSL colors and the calc()
function.
We just looked at one example where we can change the color theme of a component based on the section where it is used. But again, there is much more opportunity here when we start to get into things like letting users change themes themselves – a topic that Chris explores in this article.
What if you also wanted to adjust for yellow being perceived as much lighter than other colours? Could you add some kind of adjustment factor based on the hue?
Hue and Lightness are independent of each other; I don’t see how you could change one based on the other. If you need to overwrite the hue for a particular section you could do this:
--hue: calc(var(--first-hue) + calc(var(--hue-step) * 3) + 15); // 15 represents your adjustment
To overwrite the Lightness you would need to set
--lgt-step
for each section before you define anything else. Feel free to fork the pen and experiment with that.I am still fairly new to the concept of using CSS variables and find the topic very fascinating. Where can I get a more complete tutorial? I mainly want to be able to set up and vary the color hue and luminosity values and apply them to fonts such as headers, links, certain backgrounds to sections of text, etc.
Click on the SCSS tab on the last pen, I guess that’s what you are looking for.
Okay. I see what you are doing. I was taking another approach:
I set the variables in :root like so:
and then use the variables piece-meal to for the hsl in elements further down also in the CSS file – this is all done in a custom.css file like so:
This is the correct usage of custom properties. I would probably add some extra-properties to
:root
for things that you will likely use more often, e.g.and then use
var(--text-color)
in custom.css.BTW, if you post code examples in here, you can enclose them in a pair of triple-backticks (```) for better readability, more info here.