Dark Mode Toggle — Complete Code
/* ── CSS tokens ───────────────── */
:root {
--bg: #ffffff; --text: #111827;
--card: #f9fafb; --border: #e5e7eb;
}
:root[data-theme="dark"] {
--bg: #06080f; --text: #f1f5f9;
--card: #0d1117; --border: rgba(255,255,255,.07);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--bg: #06080f; --text: #f1f5f9;
}
}
/* ── Toggle button ────────────── */
.theme-toggle {
background: none; border: 1px solid var(--border);
border-radius: 8px; padding: 6px 10px;
cursor: pointer; color: var(--text);
}
/* ── JS ───────────────────────── */
// Read saved theme or detect OS preference
const saved = localStorage.getItem('theme');
if (saved) document.documentElement
.setAttribute('data-theme', saved);
document.querySelector('.theme-toggle')
.addEventListener('click', () => {
const current =
document.documentElement.getAttribute('data-theme')
?? (matchMedia('(prefers-color-scheme:dark)')
.matches ? 'dark' : 'light');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
Frequently Asked Questions
How do I add a dark mode toggle to HTML?
3-layer approach: 1) CSS custom properties in :root for all colours. 2) :root[data-theme='dark'] overrides the tokens. 3) 15-line JavaScript reads localStorage, falls back to prefers-color-scheme, and toggles data-theme on click while saving to localStorage. Full code in the example above. Place the JS immediately after opening body tag to prevent flash of wrong theme on reload.
How do I prevent dark mode flash on page load?
Place the theme-reading JS immediately after the opening `` tag — NOT in the footer, NOT with `defer`, NOT in an external script file. The script must run synchronously before the browser renders anything: `const t=localStorage.getItem('theme'); if(t) document.documentElement.setAttribute('data-theme',t)`. This 1-line inline script eliminates the white flash completely.
How do CSS custom properties work for dark mode?
Define all colours as CSS variables in :root: `--bg:#fff; --text:#111`. Use only variables in component styles: `body{background:var(--bg);color:var(--text)}`. To switch themes, override only the :root variables under `[data-theme='dark']` — every component inherits the new values automatically. No component-level dark mode rules needed. This is the scalable approach for any project size.
Does dark mode affect SEO?
Dark mode itself does not affect SEO ranking directly. It does affect: 1) Bounce rate — users with dark OS preference who land on a white site often leave quickly. 2) Time on page — dark mode users stay longer on comfortable pages. 3) Core Web Vitals — if dark mode implementation causes CLS (layout shift), it can hurt rankings. UIXDraft dark mode templates use CSS-only colour switching with no layout shift.
What is the best dark mode approach — CSS or JavaScript?
Best approach is CSS + JS together: CSS custom properties handle the visual switching (no JS needed for the colours themselves), JS handles: reading the saved preference from localStorage, detecting OS preference with matchMedia, toggling the data-theme attribute on user click, and saving the new preference. Pure CSS approaches using prefers-color-scheme alone don't support user override — always combine both.