Author: Phil

Phil Reynolds

August 2, 2021

Extending the Theme Switch

This is a follow on from my previous article about building a vanilla JavaScript dark mode toggle.

I started my last post by saying that dark mode is becoming more and more common. This is true, but I think we’re probably a bit past that. At I/O this year Google announced the next generation of their material design language, Material You. One of the features which grabbed my attention most here was the ability for Android 12 to set the UX color based on your wallpaper.

Why should users be limited to just a light mode or dark mode? Why not give them some more choice?

So by taking the original dark mode switch and extending it, we can give our users a load more choice! And more choice is always better, right?

Previously

We have a script which checks to see if the user has previously selected a preference, then checks to see whether they have an OS theme set, then sets either light or dark theme accordingly. We then have a button which swaps the theme and saves the user’s preference.

This is exactly what happens when you hit the theme button on this page (sun or moon symbol at thet top).

You can make it simpler (Tyler Potts' personal site has 7 lines for his night mode toggle) but I like the additionality and reliability of my solution.

Tyler’s Solution:

window.onload = () => {
    const night_toggle = document.querySelector('.night-toggle');
    
    night_toggle.addEventListener('click', () => {
        document.querySelector('body').classList.toggle('night');
    });
}

Expansion

Moving forward, the main thing I wanted to add was different colour schemes for light and dark mode. This was partially inspired by the lovely theme swapper on Jonas Downey’s site. Although Jonas made the (in hindsight, extremely smart) decision to only change the top bar of his site.

Getting the colour swapper functionality working was actually fairly straight forward. Using the same idea as previously, using JS to change a css class on the body element. I decided I was going to do most of the rainbow, so created a list: let colors = ["red", "orange", "yellow", "green", "blue", "purple"]

Then when the colour button was pressed it would get the next colour from the list then call a function to set that colour in the html.

colorBtn.addEventListener("click", function () {
    currentColor = nextColor()
    setColor(currentColor)
});
function nextColor() {
    let nextColorIndex = colors.indexOf(currentColor) + 1
    if (nextColorIndex === colors.length) nextColorIndex = 0
    return colors[nextColorIndex]
}
function setColor() {
    document.body.classList.remove(...colors);
    document.body.classList.add(currentColor);
    colorBtn.innerHTML = `Colour: ${currentColor}`;
    
    console.log("Color Set!\n", currentTheme, currentColor, currentGrad)
    localStorage.setItem("color", currentColor);
}

There’s a bit to unpick here. First of all, I was really surprised that JavaScript in 2021 has no built-in way of handling expected index out of bounds errors. Thankfully the one liner which checks to see if the next index is at the end of the list and then sets the value to zero is enough to handle that error. As we’re only incrementing by one we don’t need to worry about if the index ends up greater than that value.

As an aside, I’m British, the amount of times I’ve written “color” in these three functions is troubling!

The final function removes any values which exist in the colors list from the body class list. We then add the new colour back into the class list and update the button to indicate the current colour. Finally, we store currentColor in local storage for subsequent user visits.

On page load, if currentColor isn’t set, we set it to orange which is also the default set in the HTML (and my favourite theme), so the nextColor() function knows where to move to in the colour list.

That's the meat of the added functionality. I also added a toggle for the gradient background, which toggle a css class between grad and no-grad. This is one area where I could improve the page and just toggle grad into the class list and update the to look for grad or nothing.

You can see the current full version of the code here.

The CSS

The JavaScript updates were fairly straight forward. The same could not be said for the css. We can simplify it by using SCSS. My personal site uses Jekyll which will compile it do CSS on build, for other usages thankfully JetBrains' IDEs have a file watcher plugin which will recompile it on save.

This still doesn't make it especially simple!

We have a lot of colours to manage. I’ve split the files into three, one which looks after the layout and two for the colours.

The first of which sets up the colour variables. I use Paletton to help generate shades of the primary colours. Then these variables get added to a fairly meaty Sass Map which we can reference in the second file.

The second file, is a series of nested loops. First looping through the theme’s and then through each of the colours. Then it sets all the colours for different areas of our page. This loop means our 100-line Scss file ends up as an 800-line CSS file!

The loop looks like this:

body {
    @each $theme in $themes {
        &.#{$theme} {
            @each $color in $colors {
                &.#{$color} {

$themes and $colors are simple lists. It would be nice to not need these, as they’re a bit “magic”, I think you might be able to do this with map-deep-get but although its slightly less maintainable should we wish to add more themes, I feel as though it is quite a bit simpler!

Conclusion

So, there we have it. We’ve gone from a dark-mode toggle to a more complex theme swapper. Without any shadow of a doubt, building the css and tweaking the colour themes was the most time intensive part of this project. I’m really happy with the end result and although I’m not certain any stakeholder is ever going to want the full thing, I'm sure having a range of colours themes ready to go will be helpful!

You can play with the full theme swapper on my personal site

Let me know if this was interesting, helpful or terrible!