Skip to content

July 28, 2026 · 2 min read

"Zero flash: how ThemeProvider avoids a wrong-theme flash on every load"

"The three techniques Theme Kit uses to guarantee a user never sees a flash of incorrect theme — blocking bootstrap scripts, SSR CSS variables and cookie-based fingerprinting."

nextrscssrperformance

Every theming library has to answer the same question: what does the user see between "HTML arrives" and "JavaScript runs"? If the answer is the wrong theme, you have a flash of incorrect theme (FOIT). Theme Kit attacks this from three directions at once.

1. Resolve the theme on the server

With @theme-kit/next, the ThemeProvider is a server component. It reads the theme cookie, validates the selection against the configured themes, and resolves the initial theme before a single byte of HTML is sent:

tsx
1// app/layout.tsx
2import { ThemeProvider } from "@theme-kit/next";
3
4export default function RootLayout({ children }) {
5  return (
6    <ThemeProvider themes={themes} defaultTheme="light">
7      {children}
8    </ThemeProvider>
9  );
10}

The server-rendered <html> arrives already carrying data-theme="dark" and the correct inline --theme-color-* variables, so first paint is correct.

2. Block with a bootstrap script

Cookies aren't always there — a returning user's first request, an authenticated route, or a misconfigured proxy. For those cases Theme Kit emits a small blocking bootstrap script inside <head> that runs before first paint. It reads the persisted selection from localStorage, falls back to prefers-color-scheme, and flips data-theme before the browser paints:

ts
import { createThemeBootstrapScript } from "@theme-kit/core";

const script = createThemeBootstrapScript({ themes, defaultTheme: "light" });
// → a self-contained <script> string you can inline in <head>

3. Ship the CSS variables in the SSR HTML

Because the resolved theme's tokens are flattened on the server, the --theme-color-* variables are present in the initial HTML itself. There is no "CSS loads late" gap — the tokens the first paint needs are already there, and @theme-kit/next even pre-paints the dark-mode CSS via useServerInsertedHTML.

The result

  • First paint is always correct — no theme, no layout flash.
  • No runtime dependency for correctness — the site is themed even with JavaScript disabled.
  • Fingerprinting keeps the persisted selection honest: a cookie that names a theme you no longer ship is safely rejected.

Try it yourself — this very documentation site runs on exactly this pipeline.

"Zero flash: how ThemeProvider avoids a wrong-theme flash on every load" — Theme Kit