Adding dark / light / system theming to any React app — Next.js App Router included — with no flash of the wrong theme on first paint. A framework-agnostic next-themes alternative.
Overview
A tiny ThemeProvider, a useTheme hook and a no-flash inline script. Persists to storage, follows the OS, toggles a class or data-attribute — a framework-agnostic next-themes-lite.
getThemeScript(options?)Self-contained no-flash IIFE string for the <head>.
Tips
Inject `getThemeScript()` into your `<head>` (via `dangerouslySetInnerHTML`) so the correct theme is applied before the page paints — this is what prevents the flash.
Wrap your app in `<ThemeProvider>` and read/set with `useTheme()` → `{ theme, setTheme, resolvedTheme }`. `resolvedTheme` turns "system" into the concrete "light"/"dark".
Use `attribute="class"` for Tailwind's `dark:` variant, or a `data-theme` attribute for CSS custom-property theming.
Set `disableTransitionOnChange` to avoid every transition firing at once when the theme flips.
Watch out for
The package is a Client Component boundary (ships `"use client"`). Put `<ThemeProvider>` in a client layout, but the `getThemeScript()` string can be rendered from the server `<head>`.
Match the `storageKey`, `attribute` and `defaultTheme` you pass to `getThemeScript()` with the ones on `<ThemeProvider>`, or the pre-paint script and React will disagree.