{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "theme-toggle",
  "title": "Theme Toggle",
  "description": "A light and dark switch that survives a reload, follows the system when asked, and stays in step across tabs.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/default/theme-toggle/theme-toggle.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Monitor, Moon, Sun } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport type ThemeMode = \"light\" | \"dark\" | \"system\"\n\nexport type ThemeToggleProps = Omit<\n  React.ButtonHTMLAttributes<HTMLButtonElement>,\n  \"onChange\" | \"children\"\n> & {\n  /** Modes to cycle through. Defaults to light and dark. */\n  modes?: readonly ThemeMode[]\n  storageKey?: string\n  /** Class placed on the root element for the dark mode. */\n  darkClass?: string\n  onThemeChange?: (mode: ThemeMode) => void\n  icons?: Partial<Record<ThemeMode, React.ReactNode>>\n  labels?: Partial<Record<ThemeMode, string>>\n}\n\nconst DEFAULT_MODES = [\"light\", \"dark\"] as const\n\nconst CHANGE_EVENT = \"mischief-theme-change\"\n\nconst defaultLabels: Record<ThemeMode, string> = {\n  light: \"light mode\",\n  dark: \"dark mode\",\n  system: \"system theme\",\n}\n\nfunction prefersDark() {\n  return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n}\n\n/**\n * Applies a mode and remembers it. \"system\" clears the stored choice so the\n * page follows the operating system again, including later changes to it.\n */\nexport function applyTheme(\n  mode: ThemeMode,\n  { storageKey = \"theme\", darkClass = \"dark\" } = {}\n) {\n  const root = document.documentElement\n  const dark = mode === \"system\" ? prefersDark() : mode === \"dark\"\n\n  root.classList.toggle(darkClass, dark)\n  root.style.colorScheme = dark ? \"dark\" : \"light\"\n\n  try {\n    if (mode === \"system\") window.localStorage.removeItem(storageKey)\n    else window.localStorage.setItem(storageKey, mode)\n  } catch {\n    // Private browsing refuses storage. The class is already applied, so the\n    // choice holds for this page and is simply not remembered.\n  }\n\n  window.dispatchEvent(new Event(CHANGE_EVENT))\n}\n\nexport function ThemeToggle({\n  modes = DEFAULT_MODES,\n  storageKey = \"theme\",\n  darkClass = \"dark\",\n  onThemeChange,\n  icons,\n  labels,\n  className,\n  onClick,\n  ...buttonProps\n}: ThemeToggleProps) {\n  const subscribe = React.useCallback(\n    (callback: () => void) => {\n      const onStorage = (event: StorageEvent) => {\n        if (event.key !== storageKey) return\n\n        const stored = event.newValue\n        applyTheme(\n          stored === \"light\" || stored === \"dark\" ? stored : \"system\",\n          { storageKey, darkClass }\n        )\n        callback()\n      }\n\n      const media = window.matchMedia(\"(prefers-color-scheme: dark)\")\n\n      window.addEventListener(CHANGE_EVENT, callback)\n      window.addEventListener(\"storage\", onStorage)\n      media.addEventListener(\"change\", callback)\n\n      return () => {\n        window.removeEventListener(CHANGE_EVENT, callback)\n        window.removeEventListener(\"storage\", onStorage)\n        media.removeEventListener(\"change\", callback)\n      }\n    },\n    [storageKey, darkClass]\n  )\n\n  const read = React.useCallback((): ThemeMode => {\n    let stored: string | null = null\n\n    try {\n      stored = window.localStorage.getItem(storageKey)\n    } catch {\n      stored = null\n    }\n\n    if (stored === \"light\" || stored === \"dark\") return stored\n\n    return modes.includes(\"system\")\n      ? \"system\"\n      : document.documentElement.classList.contains(darkClass)\n        ? \"dark\"\n        : \"light\"\n  }, [storageKey, darkClass, modes])\n\n  // The server cannot know the reader's choice, so it renders the first mode\n  // and the client corrects it on hydration.\n  const serverMode = React.useCallback(() => modes[0]!, [modes])\n\n  const mode = React.useSyncExternalStore(subscribe, read, serverMode)\n  const next = modes[(modes.indexOf(mode) + 1) % modes.length] ?? modes[0]!\n\n  const label = `Switch to ${labels?.[next] ?? defaultLabels[next]}`\n  const icon =\n    icons?.[mode] ??\n    (mode === \"system\" ? (\n      <Monitor aria-hidden=\"true\" size={18} strokeWidth={1.9} />\n    ) : mode === \"dark\" ? (\n      <Moon aria-hidden=\"true\" size={18} strokeWidth={1.9} />\n    ) : (\n      <Sun aria-hidden=\"true\" size={18} strokeWidth={1.9} />\n    ))\n\n  return (\n    <button\n      type=\"button\"\n      data-slot=\"theme-toggle\"\n      data-mode={mode}\n      aria-label={label}\n      title={label}\n      className={cn(\n        \"text-muted-foreground hover:text-foreground hover:bg-muted inline-flex size-11 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent transition-colors duration-150 motion-reduce:transition-none\",\n        className\n      )}\n      onClick={(event) => {\n        onClick?.(event)\n        if (event.defaultPrevented) return\n\n        applyTheme(next, { storageKey, darkClass })\n        onThemeChange?.(next)\n      }}\n      {...buttonProps}\n    >\n      {icon}\n    </button>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "theme",
    "dark-mode",
    "control"
  ],
  "type": "registry:ui"
}