Simple Javascript Theme Toggle

This post is going to talk through setting up a theme toggle using plain JavaScript.

Dark mode is becoming more and more common, its something that most operating systems now have as an option and many individual pages now offer it as a preference too. However its not for everyone, and choice is always a good thing.

This code originally came from CSS Tricks however their implementation left a bit to be desired and just didn’t work when I tried to implement it.

With a few tweaks, we can improve their code and we have our own dark mode toggle. If you want to test this out, click the sun or moon button at the top of these page!

There’s a few considerations we need to have when building this script; does the user have a system preference set? has the user visited our site before and set a preference here?

We can check for system preferences using window.matchMedia("(prefers-color-scheme: dark)"); and we can get any previous site preference from local storage localStorage.getItem("theme");. So the first things we are going to do in our script is assign these to variables, and create a variable for our toggle button.

const btn = document.querySelector("#theme-toggle");
// check to see if OS preferences for light or dark mode
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
const prefersLightScheme = window.matchMedia("(prefers-color-scheme: light)");

// check to see if local storage has a theme preference
let currentTheme = localStorage.getItem("theme");

We want to let the user overwrite their system preferences, but if they haven’t done that we want to listen to take the system preference and set our site theme to match. We’re going to create a function called setTheme, in this we inspect our currentTheme variable. If it doesn’t exist we look at the system preference, set the currentTheme variable and call the function again.

With a currentTheme variable set, we can make sure that the class on the body corresponds to our theme variable.

function setTheme() {
    //if no local storage check against system preferences
    if (currentTheme === null) {
        if (prefersDarkScheme.matches) {
            currentTheme = "dark"
        } else if (prefersLightScheme.matches) {
            currentTheme = "light"
        } else {
            // if no preferences, default to dark theme
            currentTheme = "dark"
        }
        setTheme()
    } else if (currentTheme === "dark") {
        document.body.classList.remove("light-mode");
        document.body.classList.add("dark-mode");
    } else if (currentTheme === "light") {
        document.body.classList.remove("dark-mode");
        document.body.classList.add("light-mode");
    }
}

There’s two final bits to our script. First, handle a button press and toggle the theme. The button inspects our currentTheme variable and swaps the value. It then stores the new value in local storage. There is probably a shorter way of writing this with a ternary operator but this is clean, simple and easily understandable.

btn.addEventListener("click", function () {
    if (currentTheme === "dark") {
        currentTheme = "light"
        setTheme()
    } else {
        currentTheme = "dark";
        setTheme()
    }
    localStorage.setItem("theme", currentTheme);
});
        

The final thing we want to do is call our setTheme() function at the bottom of the page so it runs on page load. The full script is at the end of this article.

The next thing we need to do is set up the css to control our themes. With the way we’ve put this together we’re not limited to just dark or light themes, we could implement a range of colours if we wanted. For now, we’re just sticking with dark and light themes. These classes are applied to our html body. So any additional styling we want just needs to use these as parents, the scss we use for this is here:

&.dark-mode {
  background-color: $dark;
  background: $dark-background;
  color: $ink-light;

  .dark {
    display: block;
  }

  .light {
    display: none;
  }

  nav {
    a, button {
      background: #ffffffe6;
      color: $dark-gray;
    }

    a:hover {
      color: $ink-dark;
      background: #fff;
    }

    button:hover {
      color: $ink-dark;
      background: $light-background;
    }
  }

  .author {
    color: $medium-gray;
  }

  article {
    a {
      color: $dark-link;
      text-decoration: none;

      &:hover {
        color: $dark-link-hover;
      }
    }

    code.inline {
      color: $medium-gray;
    }
  }
}

&.light-mode {
  background-color: $gray;
  background: $light-background;
  color: $ink-dark;

  .light {
    display: block;
  }

  .dark {
    display: none;
  }

  .lab-logo {
    background-color: $dark;
    background: $dark-background;
    -webkit-box-shadow: 0 5px 10px 10px $dark;
    box-shadow: 0 5px 10px 10px $dark;
  }

  nav {
    a, button {
      background: $dark-gray;
      color: $ink-light;
    }

    a:hover {
      color: $dark;
    }

    button:hover {
      color: $ink-light;
      background: $dark-background;
    }
  }

  .author {
    color: $dark-gray;
  }

  article {
    a {
      color: $light-link;
      text-decoration: none;

      &:hover {
        color: $light-link-hover;
      }
    }

    code.inline {
      color: $dark-gray;
    }
  }
}

You can also view this on GitHub

If you have any questions, thoughts or suggestions, please get in touch!