Blog Add dark mode without a flash of the wrong theme
Add dark mode without a flash of the wrong theme
TL;DR Store the theme choice, apply it before the page paints with a tiny inline script in the head, and drive your colours with CSS variables switched by a data attribute or media query. The inline script is what kills the flash of the wrong theme.
Dark mode is easy to add and easy to add badly. The tell of a rushed job is the flash: the page loads white, then blinks to dark a moment later. Fixing that flash is the whole difference between a polished toggle and an annoying one.
Drive colours with variables
Define your colours as CSS custom properties and switch them by a data attribute or media query, so one change flips the whole site.
:root { --bg: #ffffff; --text: #111111; }
:root[data-theme="dark"] { --bg: #0a0e17; --text: #eaeaea; }
body { background: var(--bg); color: var(--text); }
Now the theme is a single attribute on the root element.
Kill the flash with an inline script
The flash happens because the theme is applied after the page paints. The fix is to set it before, with a small script at the very top of the <head>, before any content renders.
<script>
(function () {
try {
var t = localStorage.getItem("theme");
if (!t) t = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
document.documentElement.setAttribute("data-theme", t);
} catch (e) {}
})();
</script>
Because this runs before the body paints, the page comes up in the right theme immediately. No blink.
Respect the system, allow an override
Default to the operating system's setting with prefers-color-scheme, then let the visitor choose and remember it:
function setTheme(t) {
document.documentElement.setAttribute("data-theme", t);
try { localStorage.setItem("theme", t); } catch (e) {}
}
Your toggle calls setTheme("dark") or setTheme("light"), and the inline script picks it up on the next load.
The detail people miss
The inline script has to be inline and early. Load it as an external file or place it after content, and the flash comes back, because the browser paints before the script runs. Keep it small, keep it first, wrap it in try/catch for private-mode storage errors, and dark mode feels instant instead of glitchy.
FAQ
What causes the flash of the wrong theme?
The page renders with its default colours first, then JavaScript runs later and switches to the saved theme. That gap is the flash. Applying the theme before first paint, with an inline script in the head, removes it.
Should dark mode follow the system setting?
Default to the system preference with prefers-color-scheme, then let the user override it and remember their choice. That respects both the OS setting and the person who wants something different on your site.
Where should the theme be stored?
localStorage is the common choice for a per-visitor preference. Read it in the inline script at the top of the page so the theme is set before anything renders.