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.