Skip to main content
← 🎨 CSS as a design systemΒ·Module B5 Β· Lesson 6
TaskBuild a theme toggle. :root has light tokens; :root[data-theme='dark'] has dark overrides. HTML body has button#toggle 'Toggle theme' + p 'Click to swap'. JS: on click, toggle data-theme attribute between 'light' and 'dark' on documentElement.

Manual theme switch via data-attribute

100 XP7 min
Theory

Let the user override OS preference

<html data-theme="dark">
:root { --bg: white; --text: oklch(0.20 0 0); }
:root[data-theme="dark"] { --bg: oklch(0.18 0 0); --text: oklch(0.95 0 0); }

The selector :root[data-theme="dark"] matches when <html> has data-theme="dark". Toggle the attribute β†’ tokens swap β†’ whole page re-themes.

JS to toggle

const toggle = document.querySelector("#theme-toggle");
toggle.addEventListener("click", () => {
  const current = document.documentElement.getAttribute("data-theme");
  const next = current === "dark" ? "light" : "dark";
  document.documentElement.setAttribute("data-theme", next);
  localStorage.setItem("theme", next);     /* persist */
});

Combine with prefers-color-scheme

/* Light by default */
:root { --bg: white; }

/* Dark if user prefers it AND hasn't manually overridden */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --bg: oklch(0.18 0 0); }
}

/* Manual dark override */
:root[data-theme="dark"] { --bg: oklch(0.18 0 0); }

OS setting kicks in by default; user can force light or dark via the toggle.

Read on page load (no flash)

Before the body renders, run a tiny script that reads localStorage and sets the attribute:

<head>
  <script>
    (function() {
      const saved = localStorage.getItem("theme");
      if (saved) document.documentElement.setAttribute("data-theme", saved);
    })();
  </script>
</head>

Runs synchronously before paint β†’ no flash of light theme on a user who prefers dark.

πŸ”’

Sign up to start coding

Theory is open to everyone. The interactive editor, live preview, and check are unlocked with a 7-day free trial β€” card required, cancel anytime.

Sign up β€” free trial β†’

First 10 lessons in each track are free. No card needed for those.

← PreviousNext lesson β†’

Get one Python or web tip a day β€” by email

Short, hand-written, no spam. Unsubscribe in one click.