Dark mode toggle

Dark mode for everyone, right? If you're about to add it to your web site, there are a few things to consider:

  • How do you toggle between dark and light mode; by time of day, satisfy operating system settings or user activation?
  • How do you persist the user's choice?
  • How do you reactivate the user's preference on return?

Automagic toggle

Lately color schemes, especially dark mode, have become inherent features in devices people use. When rendering color scheme, automatically setting mode can be achieved both in CSS and JavaScript. You have the options to base mode on operating system settings or time of day.

Background by preference #

css
@media (prefers-color-scheme: dark) {
  --background-color: #222;
}

@media (prefers-color-scheme: light) {
  --background-color: #fafafa;
}

body {
  background-color: var(--background-color);
}

When using media queries, be thoughtful of feature detection and to fall back to a default color scheme.

Append class based on preference #

js
if (
  window.matchMedia &&
  window.matchMedia(
    "(prefers-color-scheme: dark)"
  ).matches
) {
  document.documentElement
    .classList.add("dark-mode");
} else {
  // fallback
  document.documentElement
    .classList.add("light-mode");
}

A friendly gesture to your user could be to switch on dark mode in the evening. It's common that I adjust screen brightness when I browse web sites with light colors in the evening.

Nighttime dark mode #

js
const hour = +new Date()
  .toLocaleTimeString(
    [],
    { hour: '2-digit', hour12: false });

if (7 <= hour && hour < 19) {
  document.documentElement
    .classList.add("light-mode");
} else {
  document.documentElement
    .classList.add("dark-mode");
}

Persist preference

It's a frequent sighting nowadays to have a dark and light switch, allowing users to chose preferred mode. Quite plausible, the user would favor the same choice on return. To persist the user's preference, it's key to consider how and when to react to the stored value.

Cookies enables server-side scaffolding the page to reflect the user's preference. The advantage of considering dark or light mode server-side, is that it enables the right mode prior to applying styles.

Another route is to save the user's choice in localStorage. The Web Storage API is only accessible in the browser, and therefor confines you to toggle accordingly client-side.

Store user's choice #

html
<style>
  :root {
    --background-color: #222;
    --text-color: #fafafa;
  }

  input[name='color-scheme'],
  input[name='color-scheme']:checked + label {
    display: none;
  }
  
  #light-mode:checked ~ .content {
    --background-color: #fafafa;
    --text-color: #222;
  }

  .content {
    background-color: var(--background-color);
    color: var(--text-color);
  }
</style>

<input type="radio" id="light-mode"
 value="light-mode" name="color-scheme">
<label for="light-mode">Light</label>
<input type="radio" id="dark-mode"
 value="dark-mode" name="color-scheme"
 checked>
<label for="dark-mode">Dark</label>

<article class="content">
  <h1>One ring</h1>
  <p>In the darkness bind them</p>
</article>

<script>
  const modes = Array.from(
    document.querySelectorAll(
      "input[name='color-scheme']"));
  
  modes.forEach(
    el => el.addEventListener(
      "click",
      event => localStorage.setItem(
        "color-scheme",
        event.target.id)));
</script>

Preference activation

Don't welcome your visitors with a flash. Leaking a default color scheme, even for a brief moment, will result in an unpleasant user experience. Consider color scheme activation without flashing when choosing toggling strategy.

CSS media queries enables rendering the page without flash, but depends on modern capabilities to exist in the user environment.

For predictable dark or light mode activation in JavaScript, first bear in mind whether the user even has JavaScript enabled. You'll also briefly be blocking rendering as the page initial state is required to be determined prior to rendering a color scheme.

Activate dark or light mode #

html
<head>
  <script>
    const colorScheme = localStorage.getItem("color-scheme") || "light-mode";
  </script>
</head>

<body>
  <input type="radio" id="dark-mode"
   value="dark-mode" name="color-scheme">
  <label for="dark-mode"
   class="color-scheme">Light</label>
  <input type="radio" id="light-mode"
   value="light-mode" name="color-scheme"
   checked>
  <label for="light-mode"
   class="color-scheme">Dark</label>

  <script type="text/javascript">
    // use preferred color scheme
    const useDark =
      colorScheme === "dark-mode";

    document.getElementById(
      "dark-mode"
    ).checked = useDark;
    
    document.getElementById(
      "light-mode"
    ).checked = !useDark;
  </script>
  <!-- ... -->
</body>

I hope I've provided some insights about things to consider when creating a dark mode toggle. Carefully consider your UI to be predictable.

For inspiration, check the following examples. Try them out with or without JavaScript enabled. For implementation details, View Page Source is your friend.

  • A widely supported example that use the Radio Button Hack to pick mode.
  • As of Safari 15.4 or later (example added 2022-04-29), you can test the :has selector, which enable better structured HTML.