Simple dark mode recipe

This page explains how to add a "dark mode" to your web page, with the following nice properties:

  1. It's just a tiny bit of handwriten CSS and JavaScript, no fancy tools or libraries or cookies.
  2. It defaults to the user's preference, but offers a toggle.
  3. If the toggle is ever used, its state is tracked in LocalStorage.
  4. It falls back gracefully if JavaScript is disabled.
  5. It animates smoothly when toggling the theme, without spuriously animating when the page loads.

Try it on this page! (And then, try refreshing.)

This button should reset the theme to your system preference:

Styles

We'll use a .dark class on the <html> tag to control dark mode. The CSS looks like this:

/* The light and dark theme color variables are defined up here: */

html, html.light {
  color-scheme: light;
  --text: #111; 
--accent: #f80;
--bg: #eef;
} html.dark { color-scheme: dark; --text: #dde;
--accent: #ec0;
--bg: #334;
} /* It sucks a little to have to define the dark colors twice, */ /* but this makes it work even when JavaScript is disabled: */ @media (prefers-color-scheme: dark) { html { color-scheme: dark; --text: #dde;
--accent: #ec0;
--bg: #334;
} } /* The rest of your page style goes below here. */ body { transition: background-color 0.2s; background-color: var(--bg); color: var(--text); /* et cetera */ }

Code

Put this code in a script tag above the body (at the end of <head>), to prevent a flash of the incorrect theme when the page first loads.

<script>
  function lget(key) { try { return localStorage.getItem(key); } catch (e) { return null; } }
  function lset(key, value) { try { localStorage.setItem(key, value); } catch (e) {} }
  function prefersDark() { return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; }
  function getTheme() { return lget("theme") || (prefersDark() ? "dark" : "light"); }
  function setTheme(theme) { lset("theme", theme); document.querySelector("html").className = theme || getTheme(); }
  function toggleTheme() { setTheme(getTheme() === "dark" ? "light" : "dark"); }
  setTheme(getTheme());
</script>

Finally, put a button like this somewhere on your page, and you're done!

<button onclick="toggleTheme()">Toggle theme</button>

You can also call setTheme('light') or setTheme('dark') or setTheme('') (follow system preference).

Here's a fancier emoji-based toggle: View this page's source code to see how it works!