Light & Dark theme

I’ve added a light/dark mode and a theme switcher to this website now (in the footer). I expect I’ll make some tweaks to the colour-scheme in time to improve the legibility & accessibility of the website.

The rest of this post explains how I implemented it.

Step 1: Add some media-queries into the CSS.

The easiest way to add dark/light theme support is with the CSS media feature prefers-color-scheme.

Using this media feature, you can specify different CSS styles based on user preference which is set at either OS or browser level.

@media (prefers-color-scheme: dark) {
    /* Dark styles go here */
}

@media (prefers-color-scheme: light) {
    /* Light styles go here */
}

Whilst I could have stopped there, I wanted to give users the option of changing the theme on-the-fly.

Unfortunately, there’s not an easy way to change this ‘prefers-color-scheme’ setting on a per website basis1. This means individual websites have to implement their own mechanism to support it.

Step 2: Add some Javascript.

In order for the user to be able to switch the theme, I’ve had to include some Javascript into each page. This will apply an extra CSS class onto the body tag of the page, adjusting the styles to switch between light & dark themes.

Unfortunately the way I’ve implemented this means that I need to duplicate the CSS for both dark & light colour-schemes.
I apply a .light class if prefers-color-scheme: dark, and a .dark class where prefers-color-scheme = light or unset).

:root {
  /* (Default) light styles go here */
}
:root.dark {
  /* Dark styles go here */
}
@media (prefers-color-scheme: dark) {
  :root {
    /* (Default) dark styles go here */
  }
  :root.light {
    /* Light styles go here */
  }
}

The above CSS is paired with the following Javascript code, which toggles between light & dark themes:

/* Identify the user preference */
var darkMode = false;
if ( window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ) {
  var darkMode = true;
}
/* Toggle the colour-scheme */
if (darkMode) {
  document.documentElement.classList.toggle("light");
} else {
  document.documentElement.classList.toggle("dark");
}

This is a good start, but when you navigate to a different page, or refresh the website the styles will revert to the prefers-color-scheme preference. That’s bad UX.

We need a way to retain the users choice for the session.

Step 3: Persistance.

How can we store the user preference without a backend? Store it on their device.

I’ve used a localStorage variable. When a user navigates to this website the default value is set to their current preference. If they toggle the colour-scheme, that variable is updated, and checked on each new page-load to apply the correct colour-scheme for the next visit, or page click.

const sessionMode = localStorage.getItem("darkmode");
if ( sessionMode == null) {
  /* Store the default value */
  localStorage.setItem("darkmode", darkMode);
} else {
  if ( sessionMode == "false" ) {
    /* Toggle .light class */
    document.documentElement.classList.toggle("light");
  } else {
    /* Toggle .dark class */
    document.documentElement.classList.toggle("dark");
  }
}

During testing, I had some of this code in a function which waited until the DOM content had finished loading, but this caused a flash-of-unstyled-text (FLOUT). I was also weary of the page-length, or load-times if the script was loaded from an external file, so I added the critical Javascript into the <head> of the page which minimises the chance of that happening.


  1. I wonder if there’s browser addons that add this ability, or if that would be possible… ↩︎

Responses (0) You can respond to this post using Webmentions.