Theme Switcher Code Generator
Design your light, dark, and custom themes visually, then generate a complete, flash-free theme switcher for React, Next.js, Vue, or vanilla JS. Choose how the theme is applied (a class on <html> or a data-theme attribute), set your persistence key and system-preference behaviour, and a live, working toggle previews the exact themes you'll ship. Export the provider/hook, the CSS variables, the inline anti-flash <head> script, or a JSON manifest.
About this ToolHow it works, benefits & use casesTap to collapse
Most theme-switcher snippets either flash the wrong theme on load or hard-code light and dark only. This generator builds a complete, flash-free theme system you can actually ship. Design as many themes as you want — light, dark, sepia, brand variants — each with its own background, surface, text, muted, border, and accent variables set through colour pickers, and a live working toggle in the sticky preview switches a real card between them as you edit. Choose how the theme is applied (a class on the <html> element, the Tailwind-friendly approach, or a clean data-theme attribute), set the localStorage key the code persists to, and decide whether the system prefers-color-scheme is respected as a fallback. The tool emits four coordinated artefacts: a framework provider/hook (React Context + useTheme, a Next.js App Router provider with an exported anti-flash script, a Vue composable, or a vanilla ThemeManager class), the CSS :root and theme-scoped custom properties, the inline <head> script that applies the stored theme before first paint, and a JSON manifest of every theme. A built-in validator flags duplicate ids, a missing default, or a system-preference setting with no matching theme, and the whole configuration lives in a shareable URL.
How to Use
- 1Start from a preset (Classic, Midnight, Solarized, or three themes) or edit the default light and dark themes.
- 2For each theme set its name, mode (light/dark), and its six colour variables with the pickers; add or remove themes as needed.
- 3Pick a framework — React, Next.js, Vue, or vanilla JS — and an apply strategy: class on <html> or a data-theme attribute.
- 4Set the localStorage key and toggle whether the system colour-scheme preference is honoured as a fallback.
- 5Use the live preview to switch between your themes and confirm they look right.
- 6Choose an output target (provider, CSS, flash script, or manifest) and copy or download it; share the URL to hand off the exact setup.
Key Benefits
- Flash-free: an inline <head> script applies the stored theme before first paint, eliminating the flash of incorrect theme
- Unlimited themes, not just light/dark — each with background, surface, text, muted, border, and accent variables
- Live, interactive toggle previews your exact themes on a real card and buttons
- Two apply strategies: a class on <html> (Tailwind-friendly) or a clean data-theme attribute
- Four coordinated outputs: framework provider/hook, CSS variables, the anti-flash script, and a JSON manifest
- Targets React (Context + hook), Next.js App Router, Vue (composable), and vanilla JS (ThemeManager)
- Configurable persistence key and optional prefers-color-scheme fallback with matching @media blocks
- Built-in validator catches duplicate ids, a missing default, and system-preference mismatches; shareable URL
Common Use Cases
- Adding a robust, no-flash dark/light toggle to a React, Next.js, Vue, or vanilla project
- Shipping more than two themes (light, dark, sepia, high-contrast) with a single picker
- Generating a Tailwind-compatible class strategy or a tidy data-theme attribute setup
- Producing the exact inline <head> script needed to prevent the flash of incorrect theme in SSR apps
- Exporting a JSON manifest of themes for a design system or a theming admin UI
- Standardising theme persistence and system-preference handling across multiple apps
Live preview
Provider / hook · React · class
'use client';
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
type Theme = 'light' | 'dark';
interface ThemeContextValue {
theme: Theme;
setTheme: (theme: Theme) => void;
toggle: () => void;
themes: { id: Theme; label: string }[];
}
const THEMES: { id: Theme; label: string }[] = [
{ id: 'light', label: 'Light' },
{ id: 'dark', label: 'Dark' },
];
const STORAGE_KEY = 'theme';
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
function applyTheme(theme: Theme) {
var root = document.documentElement;
root.classList.forEach(function (c) { if (c.indexOf('theme-') === 0) root.classList.remove(c); });
root.classList.add('theme-' + theme);
root.setAttribute('data-theme', theme);
}
export function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setThemeState] = useState<Theme>('light');
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
if (stored && THEMES.some((t) => t.id === stored)) {
setThemeState(stored);
}
else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
setThemeState('dark');
}
}, []);
useEffect(() => {
if (!mounted) return;
applyTheme(theme);
try { localStorage.setItem(STORAGE_KEY, theme); } catch {}
}, [theme, mounted]);
const value: ThemeContextValue = {
theme,
setTheme: setThemeState,
toggle: () => {
const ids = THEMES.map((t) => t.id);
const i = ids.indexOf(theme);
setThemeState(ids[(i + 1) % ids.length]);
},
themes: THEMES,
};
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');
return ctx;
}
export function ThemeSwitcher() {
const { theme, setTheme, themes } = useTheme();
return (
<select
aria-label="Theme"
value={theme}
onChange={(e) => setTheme(e.target.value as Theme)}
>
{themes.map((t) => (
<option key={t.id} value={t.id}>{t.label}</option>
))}
</select>
);
}
Start from a preset
Framework & strategy
Themes
Persistence & system
The generated code reads and writes the active theme under this key.
When nothing is stored, fall back to prefers-color-scheme (and emit matching @media blocks in the CSS).
Output
The framework theme controller — context + hook, composable, or manager class.
Why a flash-prevention script matters
React, Vue, and Next.js apply the saved theme after JavaScript loads, so the page paints once with the default theme and then snaps to the real one — the dreaded flash of incorrect theme. The inline <head> script runs before first paint, reading localStorage and setting the class or attribute so the correct theme is there from frame one.
Class vs data-theme
The class strategy toggles .theme-dark on <html> (and still mirrors a data-theme attribute) — handy with Tailwind's class dark mode. The data-theme strategy sets only the attribute, which keeps your CSS selectors tidy when you have more than two themes.
Was this tool helpful?
Share Your Experience
Help others discover this tool!
Related tools
- CSS FormatterFormat and beautify CSS code
- TypeScript FormatterFormat and beautify TypeScript code
- Tailwind Config GeneratorVisually build a Tailwind config - theme extend, colors, spacing, fonts, screens, plugins - and export it as a v3 JS config or a v4 @theme CSS block
- Component Name GeneratorTurn a description into ranked, kind-aware component names with a file scaffold and a name validator (casing, collisions, clarity)
- Spacing Scale CalculatorLinear, geometric, Fibonacci or modular spacing scales with a visual ruler preview and utility classes — export CSS, Tailwind v4, SCSS, JSON
- Badge GeneratorBuild shields.io README badges with live preview, assemble a badge row, and export Markdown, HTML, reStructuredText or AsciiDoc
It generates a tiny inline script meant for your document <head>. Because it runs synchronously before the browser paints, it reads the stored theme from localStorage (and optionally the system preference) and sets the class or data-theme attribute on <html> immediately — so the page renders in the correct theme from the very first frame instead of defaulting to one theme and snapping to another after JavaScript loads. For Next.js the provider also exports this script as a string so you can inject it in your root layout.
The class strategy adds a class like theme-dark to the <html> element (and also mirrors a data-theme attribute), which pairs well with Tailwind’s class-based dark mode. The data-theme strategy sets only a data-theme="dark" attribute on <html>. Both let your CSS scope theme variables with selectors like [data-theme="dark"] or .theme-dark; data-theme tends to stay tidier once you have more than two themes.

