consolelog.tools

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 cases

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

  1. 1Start from a preset (Classic, Midnight, Solarized, or three themes) or edit the default light and dark themes.
  2. 2For each theme set its name, mode (light/dark), and its six colour variables with the pickers; add or remove themes as needed.
  3. 3Pick a framework — React, Next.js, Vue, or vanilla JS — and an apply strategy: class on <html> or a data-theme attribute.
  4. 4Set the localStorage key and toggle whether the system colour-scheme preference is honoured as a fallback.
  5. 5Use the live preview to switch between your themes and confirm they look right.
  6. 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

Configuration looks good

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