Dark mode without the flash
There’s a particular kind of dark-mode implementation that I find genuinely annoying: the one that loads in light mode, paints the page, and then snaps to dark a fraction of a second later. The cure is small and worth writing down.
The trick
The flash happens because the JavaScript that reads the user’s preference runs
after the first paint. The fix is to do that work in a tiny inline script in
the document <head>, before the body is rendered.
<script>
(function () {
var stored = localStorage.getItem("theme");
var prefersDark = matchMedia("(prefers-color-scheme: dark)").matches;
var theme = stored || (prefersDark ? "dark" : "light");
document.documentElement.dataset.theme = theme;
document.documentElement.style.colorScheme = theme;
})();
</script>
Two things matter here:
- It’s synchronous and inline, so it runs before paint.
- It writes to
data-themeon<html>, which I use as the hook for all my CSS custom property overrides.
CSS side
The CSS is just a token swap:
:root {
--bg: #fbf8f3;
--fg: #1f1b16;
}
:root[data-theme="dark"] {
--bg: #0e0d0b;
--fg: #f4f1ea;
}
body {
background: var(--bg);
color: var(--fg);
}
That’s the whole thing. No framework, no flicker, no third-party script.