Skip to content

Vite Plugin

Optional: zero-flash before the bundle loads

The themeKitVitePlugin injects a synchronous bootstrap script into your index.html at build time so the persisted theme is applied on the very first frame — before the JavaScript bundle even loads.

1Why a Vite Plugin?

Providers are already flash-proof; the plugin covers the pre-bundle frame.
Do you still need this?

Since @theme-kit/react (and Vue, Svelte, Solid) 1.2.0, the providers are flash-proof out of the box — they inject the blocking bootstrap themselves before first paint. The vite plugin is only needed if you want the theme applied on the absolute first frame before the bundle loads (relevant for slow bundles or network-sensitive previews).

  • Pre-bundle first frame — the bootstrap script runs synchronously from index.html before the bundle loads, so even the raw HTML frame is themed.
  • No runtime overhead — the script is injected at build time and does not ship as a separate chunk. It runs once and is never re-evaluated.
  • SSR-compatible — works alongside server-rendered frameworks (Next.js, Nuxt, Remix) by mirroring the same bootstrap in the server-rendered HTML.
  • Tree-shakeable — only the theme definitions you pass in are included in the bootstrap output. No unused code reaches the client.
why it matters — zero-flash explained
ts
1// Without the plugin:
2//   1. Browser loads HTML → default theme rendered
3//   2. JS hydrates → reads localStorage → swaps theme
4//   3. User sees a flash of the wrong theme
5//
6// With the plugin:
7//   1. Build injects the bootstrap script at the top of <head>
8//   2. Browser loads HTML → script runs synchronously before render
9//   3. data-theme is set on <html> before the first paint
10//   4. CSS picks up the correct custom properties immediately
11//   5. No flash, ever

2Setup

Drop the plugin into your Vite config alongside your framework plugin.
vite.config.ts
ts
1import { defineConfig } from "vite";
2import react from "@vitejs/plugin-react";
3import { themeKitVitePlugin } from "@theme-kit/core/vite";
4import { customThemes } from "./src/themes";
5
6export default defineConfig({
7  plugins: [
8    react(),
9    themeKitVitePlugin({
10      themes: customThemes,
11      defaultTheme: "light",
12    }),
13  ],
14});
Import path |The plugin is exported from @theme-kit/core/vite, not the main entry point. This keeps the core bundle clean when you only need the runtime.

3What It Does

The plugin reads your theme definitions and produces an inline script that sets the correct theme before paint.
  • Reads the theme definitions and localStorage key at build time.
  • Generates a self-contained IIFE that checks localStorage, falls back to the default theme, and sets data-theme on <html>.
  • Injects the script as <head>-prepend with enforce: "pre" so it runs before any other head scripts.
index.html — what gets injected
html
1<!doctype html>
2<html lang="en">
3  <head>
4    <!-- Injected by the plugin at build time -->
5    <script>
6      /* Theme Kit bootstrap — runs before first paint */
7      (function() {
8        var key = "theme-selection";
9        var stored = localStorage.getItem(key);
10        var theme = stored || "light";
11        document.documentElement.setAttribute("data-theme", theme);
12      })();
13    </script>
14    <title>My App</title>
15  </head>
16  <body></body>
17</html>

4Options

Full control over which themes are bundled, the default selection, and the storage key.
themeKitVitePlugin — all options
ts
1import { themeKitVitePlugin } from "@theme-kit/core/vite";
2
3themeKitVitePlugin({
4  // All theme definitions registered with the runtime
5  themes: customThemes,
6
7  // Theme applied on first visit (before localStorage)
8  defaultTheme: "light",
9
10  // OS-level mode: "light" | "dark" | "system"
11  initialMode: "system",
12
13  // Scoped family to activate initially
14  initialFamily: "brand",
15
16  // localStorage key for the persisted selection
17  // Default: "theme-selection"
18  storageKey: "theme-selection",
19
20  // CSS custom property prefix
21  // Default: "theme-"
22  prefix: "theme-",
23});
Options reference
themesRequired. The theme definitions to include in the bootstrap.
defaultThemeTheme applied on first visit before localStorage has a value.
initialModeOS-level color scheme hint. Defaults to "system".
initialFamilyScoped family to activate on first visit.
storageKeylocalStorage key for the persisted selection. Defaults to "theme-selection".
prefixCSS custom property prefix. Defaults to "theme-".

5SSR Integration

For SSR frameworks, mirror the same bootstrap in server-rendered HTML to avoid a hydration mismatch.

The Vite plugin only transforms index.html, which is not used by SSR frameworks. Instead, embed the same bootstrap script directly in your server-rendered markup so the data-theme attribute is present on the first server-painted HTML.

Next.js
next.config.mjs — Next.js integration
ts
1// The Vite plugin is framework-agnostic, but SSR frameworks
2// need the bootstrap on the server-rendered HTML too.
3// For Next.js, inject the script in your root layout:
4
5// app/layout.tsx
6const ThemeScript = () => (
7  <script
8    dangerouslySetInnerHTML={{
9      __html: `
10        (function() {
11          var key = "theme-selection";
12          var stored = localStorage.getItem(key);
13          var theme = stored || "light";
14          document.documentElement.setAttribute("data-theme", theme);
15        })();
16      `,
17    }}
18  />
19);
20
21export default function RootLayout({ children }) {
22  return (
23    <html lang="en" suppressHydrationWarning>
24      <head>
25        <ThemeScript />
26      </head>
27      <body>{children}</body>
28    </html>
29  );
30}
Nuxt
nuxt.config.ts — Nuxt integration
ts
1export default defineNuxtConfig({
2  app: {
3    head: {
4      script: [
5        {
6          innerHTML: `
7            (function() {
8              var key = "theme-selection";
9              var stored = localStorage.getItem(key);
10              var theme = stored || "light";
11              document.documentElement.setAttribute("data-theme", theme);
12            })();
13          `,
14        },
15      ],
16    },
17  },
18});
Hydration safety |Always add suppressHydrationWarning to the <html> element. The server cannot know what the client has in localStorage, so the attribute will differ on first render.
Vite Plugin — Theme Kit