VisorVisor
ComponentsGeneral

ThemeSwitcher

Fixed-position pill switcher for theme and dark/light mode, with an extras slot for hosting devtools chrome. Built with CSS Modules — copy it into your project and own it completely.

ThemeSwitcher is a fixed bottom-right pill that toggles between configured themes and dark/light mode. It owns the visor-theme and visor-color-mode localStorage keys so a no-flash script in the host layout can restore selections before paint. The optional extras slot lets host apps mount dev-only chrome (e.g., <SourceInspectorToggle />) without coupling Visor to dev dependencies.

Preview

The component is normally fixed to the bottom-right of the viewport. The preview below uses an inline style override (position: relative) so it stays inside the demo frame.

Installation

npx visor add theme-switcher

This copies two files into your project:

  • components/ui/theme-switcher/theme-switcher.tsx — the component
  • components/ui/theme-switcher/theme-switcher.module.css — the styles

Usage

import { ThemeSwitcher } from '@/components/ui/theme-switcher/theme-switcher';

const themes = [
  { id: 'default', label: 'Default', bodyClass: '' },
  { id: 'blackout', label: 'Blackout', bodyClass: 'blackout-theme' },
];

export function AppShell({ children }) {
  return (
    <>
      {children}
      <ThemeSwitcher themes={themes} />
    </>
  );
}

Pass an empty list (or omit the themes prop entirely) when only the dark/light Mode segment is needed:

<ThemeSwitcher />

Extras slot

The extras slot renders inside the pill, after the Mode segment. Use it to mount dev-only chrome:

import { ThemeSwitcher } from '@/components/ui/theme-switcher/theme-switcher';
import { SourceInspectorToggle } from '@/components/devtools/source-inspector/source-inspector-toggle';

<ThemeSwitcher
  themes={themes}
  extras={<SourceInspectorToggle />}
/>;

Avoiding theme flash

ThemeSwitcher reads localStorage on mount, which means the very first paint shows the default theme before React hydrates. To prevent the flash, run a tiny inline script in the host layout that reads the same visor-theme and visor-color-mode keys and applies the corresponding body/html classes before React boots. The pattern is framework-specific; libraries like next-themes ship one for Next.js.

Props

No props data available for “”.

ThemeOption

interface ThemeOption {
  /** Stable id used in localStorage. */
  id: string;
  /** Label rendered in the Theme segment. */
  label: string;
  /** Body class applied for this theme. Empty → no class (Visor stock). */
  bodyClass: string;
}