Implementing light and dark mode with switcher (part 3)

19
March, 2024
Marija Ercegovac

Find out how to add manual theme switch functionality with JavaScript.

In the first part, we talked about how color-scheme and prefers-color-scheme work. In the second part, we saw how to use color-scheme and prefers-color-scheme to implement automatic theme switching dependent on the OS preference on our website. Now let's see how to implement the manual switcher functionality. 

Switcher for light and dark mode

Implementation

In the previous article, we've set up the CSS so the browser can detect our operating system (OS) mode preference.

Picture this: you are at your computer and it is getting dark outside. You want to see everything in dark mode to make it easy on your eyes so you go to your computer settings and set it to "Dark". Now, wouldn't it be great if all the pages you are browsing would be in the dark mode, too, so there is no flash of brightness when you open them? That is exactly what we set up in the second partwe've told the browser to listen to our OS preference and render the page in our theme of choice so that when you open this page, it will be in the mode you've selected on your computer! Magic! 

Next, we want two things:

  • implement a switcher for a manual theme change
  • make the page remember which was your last preferred mode before you left

Technologies used in this article:

  • HTML
  • JavaScript

HTML

First, let's add the markup.

Since the label has no text, it is invisible to the screenreader, so aria-label was added for accessibility. 

<span class="switcher">
    <input type="checkbox" class="checkbox" id="input_2">
        <label for="input_2" aria-label="Dark mode switcher">
            <i class="icon-brightness_2 moon"></i>
            <i class="icon-wb_sunny sun"></i>
        </label>
</span>

JavaScript

To make the page remember the chosen mode, we will work with localStorage. As the starting point, we have focused on one information source. It will return dark or light, depending on your OS preference. 

function getOSTheme() {
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

We also made a function that will read the localStorage value and return dark or light value.

function getLocalStorageValue() {
  const theme = localStorage.getItem("theme");

  if (theme === null ) return;

  return theme === "dark" ? "dark" : "light";
}

toggle_dark_mode.js:

const html = document.documentElement;

// reads and return OS theme preference
function getOSTheme() {
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}

// reads and returns localStorage value
function getLocalStorageValue() {
  const theme = localStorage.getItem("theme");

  if (theme === null ) return;
  return theme === "dark" ? "dark" : "light";
}

function setTheme(theme) {
  if (!theme) return;

  // saves theme to localStorage
  localStorage.setItem('theme', theme);

  // adds classes on :root
  html.classList.toggle("dark", theme === 'dark');
  html.classList.toggle("light", theme !== 'dark');
}

// checks localStorage value
// if localStorage is empty, reads OS theme preference
// sets theme oposite to read value
function updateTheme() {
  const currentTheme = getLocalStorageValue() || getOSTheme;
  const newTheme = currentTheme === "light" ? "dark" : "light";

  setTheme(newTheme);
}

// sets theme ONLY if it is saved in localStorage
setTheme(getLocalStorageValue());

// listens for clicks on hidden input and updates the theme
document.addEventListener('change', function(e){
  const isSwitcher = e.target.closest('.switcher .checkbox');
  
  if(isSwitcher) updateTheme()
})

Saving user's preference via localStorage

When the user lands on the page for the first time, the browser will only read OS preference and render the page accordingly. After the first click on the switcher, the theme is switched, its class is put on the :root and saved to the localStorage so that each time the user returns, it will see the page in the last theme he chose.

setTheme(theme) will accept the theme argument and use it to:

  1. save the theme to localStorage
  2. toggle .dark and .light classes on :root.

Toggling the theme

Switching works by clicking on the moon and sun icons. We have set the event listener on the whole document, but only when the checkbox behind the icons is clicked, updateTheme() is called.

document.addEventListener('change', function(e){
  const isSwitcher = e.target.closest('.switcher .checkbox');
  if(isSwitcher) updateTheme() 
})

Testing in Browser

To help you test your code you can combine switching your themes in the OS and also use dev tools light/dark mode testers, which are slightly different in each browser:

Chrome

theme testing in chrome

Firefox

theme testing in firefox

Safari

theme testing in safari

And voila, we have our theme toggle button! You can try it yourself in the upper right corner of this page.