Easy CSS theming with variables
Tags: css, design, javascript, html
Published: 2019-08-15 17:13:17

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.