This is expansive, only do on the body background at max.
Info
The following works for theme preferrence on a single device. In order to sync preference across multiple devices, we simply need to log theme elsewhere than in localStorage, i.e into a user profile database for example.
Demo
<head> <script> (()=>{ const saved = localStorage.getItem("theme"); const prefers = matchMedia("(prefers-color-scheme: dark)").matches; const theme = saved ?? (prefers ? "dark" : "light"); document.documentElement.setAttribute("data-theme", theme); })(); </script></head><body> <h1>Title</h1> <p class="hero-sub"> Click the toggle. Reload. No flash. Resize. Open DevTools, change the OS theme. Everything updates. <strong>This is the whole system.</strong> </p> <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme"> <span class="toggle-light">Light</span> <span class="toggle-dark">Dark</span> </button></body>
:root { /* inform browser this page support both themes. */ /* If not defined, light-dark() values default to light */ /* Bonus: this allows native form controls to pickup dark theme */ color-scheme: light dark; /* Semantic tokens */ --bg: light-dark (#f5f5f5, #111111); --text: light-dark (#111111, #f5f5f5); /* add any other tokens needed */}/* Manual override based on user input */:root[data-theme="light"] { color-scheme: light; --bg: #f5f5f5; --text: #111111; /* override any other tokens defined above */}:root[data-theme="dark"] { color-scheme: dark; --bg: #111111; --text: #f5f5f5; /* override any other tokens defined above */}html, body { background: var(--bg); color: var(--text); min-height: 400px; transition: background-color 300ms ease, color 300ms ease;}:root[data-theme="light"] .toggle-light, :root[data-theme="dark"] .toggle-dark { display: none;}:root[data-theme="light"] .toggle-dark, :root[data-theme="dark"] .toggle-light { display: inline;}
const toggle = document.getElementById("themeToggle");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);});