Easy CSS theming with variables
Alex Sinclair
Posted on August 15, 2019
I recently had to tackle theming for two projects, and here is what I learned,
CSS Variables are fantastic
--colour-blue = #0074D9;
--colour-blue-dark = #001F3F;
--colour-blue-light = #7FDBFF;
What we've done here is define what our colour blue looks like. So any time we need a blue, we've got standard ones!
"But wait," I hear you say, "that's just a standard colour palette! What's that got to do with theming?"
CSS Variables can reference other variables
--colour-primary = var(--colour-blue);
--colour-primary-active = var(--colour-blue-light);
--colour-primary-disabled = var(--colour-blue-dark);
What we've done here is split the palette colours from the design colours. (If anybody knows the proper names of these, please educate me!)
This means we are in a place to swap out our variables, and CSS will just... Work.
Applying the theme
First things first! CSS Variables are scoped to whatever class they're in. I want to apply it to the whole dom, because I'm just declaring variables - but you can assign it to whatever your top element is.
:root {
--colour-blue = #0074D9;
--colour-blue-dark = #001F3F;
--colour-blue-light = #7FDBFF;
--colour-orange = #FF851B;
--colour-orange-dark = #FF4136;
--colour-orange-light = #FFDC00;
--colour-primary = var(--colour-blue);
--colour-primary-active = var(--colour-blue-light);
--colour-primary-disabled = var(--colour-blue-dark);
}
By putting the primary colours there, we've defined a default theme. This means if no other theme is applied, this will take effect. You could also do what I do, which is not define a default theme. That makes it more obvious while developing if you've missed something, I think.
Next, we define our themes as simple classes
.theme-light {
--colour-primary = var(--colour-blue);
--colour-primary-active = var(--colour-blue-light);
--colour-primary-disabled = var(--colour-blue-dark);
}
.theme-dark {
--colour-primary = var(--colour-orange);
--colour-primary-active = var(--colour-orange-light);
--colour-primary-disabled = var(--colour-orange-dark);
}
Now, on our root element we can define the default theme
<div class='theme-light'></div>
Previewing Themes
Turns out if you apply a theme to an element, that theme takes precedence over the top-level theme. So if you wanted to display a theme switching button in the theme it will switch to, it's as simple as
<div class='theme-light>
<button>Some Button</button>
<button class='theme-dark'>Dark Theme</button>
</div>
Switching Themes
To switch themes is to just replace each usage of one theme with another. This is fairly simple to do in JS - you can either keep a list of theme components and query them directly, or you can use the newer document.querySelectorAll()
method.
function toggleTheme() {
const lightElements = document.querySelectorAll('.theme-light');
const darkElements = document.querySelectorAll('.theme-dark');
lightElements.forEach(element => {
element.classList.remove('theme-light');
element.classList.add('theme-dark');
})
darkElements.forEach(element => {
element.classList.remove('theme-dark');
element.classList.add('theme-light');
})
And to trigger it:
<button onclick='toggleTheme()'>Toggle Theme</button>
Remembering User Theme Preference
This is a slight tricky one, if you're doing client side rendering. It's probably tricky if you're doing SSR too - but I don't know how you'd do that. Cookies or custom http headers maybe? Anyway!
To store the theme
I used local storage, but a cookie would work too. To store what theme the user has selected, in the toggle theme function you can add this line:
localStorage.setItem('theme', 'theme-dark')
To apply the theme
If you wait for the page to be fully loaded before applying the users' theme, you'll get a flash of the initial theme - and that's not good! I don't think it's perfect, but I put a cheeky script tag directly below my theme element in my html.
<div id='theme-element' class='theme-light'>
<script>const el = document.getElementById('theme-element')
el.classList.remove('theme-light')
el.classList.add(localStorage.getItem('theme') || 'theme-light')
</script>
I bet if you're using React or similar, you can probably just do it in the component.
<div className={localStorage.getItem('theme') || 'theme-light}>
Use Variables by area, not result
In the first project I themed, I'd already written the CSS beforehand, and had to retrofit the theming. This lead to a problem - areas that shared a colour in one theme, did not in another. This meant I couldn't just replace blue
with orange
! In the end I wound up naming the areas, and setting them in the theme.
For example, I'd used
.header {
background-color: var(--color-blue-dark);
}
.dropdown-list {
background-color: var(--color-blue-dark);
}
The problem here is that now we wanted the header to be grey, and the drop-down list to be orange!
In the end I wound up with
.header {
background-color: var(--header-colour-background);
}
.dropdown-list {
background-color: var(--dropdown-colour-background);
}
.theme-light {
--header-colour-background = var(--color-blue-dark);
--dropdown-colour-background = var(--color-blue-dark);
}
.theme-dark {
--header-colour-background = var(--color-grey-light);
--dropdown-colour-background = var(--color-orange);
}
Is there better ways of achieving this? I bet there are! If you've got any input, I'd love to hear it.
Posted on August 15, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.