Lab· Exploring·
Theme without a flash
I wanted light and dark themes that respect the system, remember a choice, and do not paint the wrong colors for a frame.
I wondered how little JavaScript it takes to get a theme toggle that does not lie on first paint.
The usual failure is obvious: the HTML arrives, CSS assumes light, then a script reads localStorage and the page snaps. People who chose dark mode see a flash. People who never chose anything should still get prefers-color-scheme.
What I tried
A tiny inline script in <head> runs before first paint, reads storage, falls back to the system preference, and sets data-theme on documentElement. CSS tokens key off that attribute. Tailwind sees the same variant.
The toggle is a button. It sets aria-pressed, writes storage, and updates color-scheme so native form controls match.
const stored = localStorage.getItem("theme");
const prefersDark = matchMedia("(prefers-color-scheme: dark)").matches;
const theme =
stored === "light" || stored === "dark"
? stored
: prefersDark
? "dark"
: "light";
document.documentElement.dataset.theme = theme;
What I learned
The inline script is the whole trick. Bundled modules run too late. Everything else is naming: tokens, a dark Shiki theme, and not animating the color change for people who asked for reduced motion.
This is still an experiment in the sense that I will keep adjusting contrast. The mechanism is simple enough to keep.