Painting the right theme before the first frame
Why the dark palette ships in CSS, and what the four lines of inline script are actually for.
A theme flash is not a rendering quirk. It is a page admitting that it decided what colour to be after it had already been drawn.
Do it in CSS
The dark palette is emitted twice: once for [data-theme="dark"], and once
under @media (prefers-color-scheme: dark) for :root:not([data-theme="light"]).
A visitor whose system is dark gets dark on the first paint, before any script
has run — before the bundle has even been requested.
The one case CSS cannot see
A stored choice that differs from the operating system. That is what the
inline snippet in the theme’s <head> covers, and it is why the snippet has to
be inline: an external or deferred script runs after the first paint, which is
the whole problem.
<script>
try {
var retroTheme = localStorage.getItem('retro-theme');
if (retroTheme) document.documentElement.setAttribute('data-theme', retroTheme);
} catch (e) {}
</script>The try is not defensive theatre. localStorage throws outright in a
sandboxed iframe and with third-party storage blocked, and an uncaught throw
here takes the rest of the head with it.