On one of my websites I decided it would be nice if the user had a choice in what colour theme the website used. I decided to have a look at how other websites did this and came across this article which explains some popular approaches.
To start off with I would just want a theme that could toggle between light and dark. But I wanted to the option to add more themes in the future.
The way that I would implement this was to use css variables and classes to change the colours used throughout the website.
I would start by defining a list of colour variables in my css that would be used by default. To get them to be used throughout the website they would be set under the :root. e.g.
/* These variables will be accessible throughout the rest of the css */
:root {
--colour-example-one: blue;
--colour-example-two: red;
--colour-example-three: green;
}
/* For example to change element colours */
p {
background: var(--colour-example-one);
}
Then I could override these variables to use a different colour pallet. I did this by changing the class on the body element. Then by adding the corresponding css.
/* A new theme can be created by specifying the class on the body element */
body.dark-theme {
--colour-example-one: darkblue;
--colour-example-two: darkred;
--colour-example-three: darkgreen;
}
// Changing the theme just requires adding the theme class to the body element
const page = document.body;
page.className = 'dark-theme';
You could then add an event onto an element of choice to trigger changing the theme. This could be a toggle button, or a dropdown list.
The choice of theme should be remembered to provide a consistent user experience, and to stop the user having to manually change the theme every time they visit. The easiest way to do this for my site was to just store the choice of theme in localStorage.
// Setting the data
localStorage.setItem('theme', theme);
// Getting the data we need to make sure it's been set
let theme = localStorage.getItem('theme');
if (theme !== undefined) {
page.className = theme;
}
When the user loads the website we need to decide what theme should be set. Originally I was just going to have this set to the theme stored in localStorage, or the dark theme if nothing existed in storage.
But I decided to also consider the users system theme, this can be accessed using the prefers-color-scheme media feature. Below is an example of checking this in javascript, the values are either dark or light.
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
// Set light theme
} else {
// Set dark theme
}
This turned out to not be that hard to implement and was mainly focused around changing classes and css values.
The only thing I wasn't too sure about keeping was setting the default colour scheme from the users system theme. The reason being that most users will probably just have the default light theme without really thinking about it. So if there was a theme that the site looked a lot nicer in, then this should probably be the default rather than the users system theme.
At the time of writing this the theme changing functionality is on this website, so feel free to take a look.
I noticed a slight issue with the approach I'd done. When you load the website it first loads the default colour theme very briefly before changing it to the saved one. This very quick colour change doesn't affect much, but it doesn't look very appealing. The reason for this is that the initialisation of the theme is done on the window onload event. Which would be after the default theme has already loaded.
To get around this I set the theme class on the html element (document.documentElement) rather than the body (document.body), this allowed me to initialise the theme within a script tag before the body had loaded. Then I could switch the css variable definitions to be under the html rather than body. e.g.
/* The html replaces the body element */
html.dark-theme {
--colour-example-one: darkblue;
--colour-example-two: darkred;
--colour-example-three: darkgreen;
}
The rest of the page should then use the correct colour variables as the class has already been set on the html element. I'm not sure if this will cause other issues, but it does seem to work a little better.
The other solution I found was to use the css perfers-color-scheme method within the css. This would be a little more robust, but it only allows for two options of themes. Whereas the solution I have allows for any number of themes. It would also make changing themes manually more complex.
@media (prefers-color-scheme: dark) {
body {
--colour-example-one: darkblue;
--colour-example-two: darkred;
--colour-example-three: darkgreen;
}
}