Modurotolu Olokode

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.