javascript 43 lines · 8 steps

Building a dark-mode toggle that respects the OS

A theme switcher that honors the system preference until the user explicitly overrides it, then remembers that choice.

Explained by highlit
1const STORAGE_KEY = "theme-preference";
2 
3function getSystemTheme() {
4 return window.matchMedia("(prefers-color-scheme: dark)").matches
5 ? "dark"
6 : "light";
7}
8 
9function getStoredTheme() {
10 return localStorage.getItem(STORAGE_KEY);
11}
12 
13function applyTheme(theme) {
14 document.documentElement.dataset.theme = theme;
15 const toggle = document.querySelector("[data-theme-toggle]");
16 if (toggle) {
17 toggle.setAttribute("aria-pressed", String(theme === "dark"));
18 }
19}
20 
21function resolveTheme() {
22 return getStoredTheme() ?? getSystemTheme();
23}
24 
25function initThemeToggle() {
26 applyTheme(resolveTheme());
27 
28 const systemQuery = window.matchMedia("(prefers-color-scheme: dark)");
29 systemQuery.addEventListener("change", (event) => {
30 if (getStoredTheme() === null) {
31 applyTheme(event.matches ? "dark" : "light");
32 }
33 });
34 
35 const toggle = document.querySelector("[data-theme-toggle]");
36 toggle?.addEventListener("click", () => {
37 const next = resolveTheme() === "dark" ? "light" : "dark";
38 localStorage.setItem(STORAGE_KEY, next);
39 applyTheme(next);
40 });
41}
42 
43document.addEventListener("DOMContentLoaded", initThemeToggle);
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Fall back to the OS preference only until the user makes an explicit choice, then persist it.
  2. 2Nullish coalescing cleanly expresses 'stored value wins, otherwise derive a default'.
  3. 3Keeping DOM state and ARIA attributes in one apply function keeps visuals and accessibility in sync.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a dark-mode toggle that respects the OS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code