{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "theme-toggle",
  "type": "registry:component",
  "title": "Theme toggle",
  "description": "Switches between light and dark schemes. The icon changes and the choice persists locally.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/icons.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/theme-toggle.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { Icon } from \"./icons\";\n\nexport interface ThemeToggleProps\n  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n  /** localStorage key the choice persists under. */\n  storageKey?: string;\n  onThemeChange?: (dark: boolean) => void;\n}\n\nconst styles = stylex.create({\n  toggle: {\n    transition: {\n      default: vlak.transition,\n      [mq.reduce]: \"none\",\n    },\n    position: \"fixed\",\n    top: \"1.5rem\",\n    insetInlineEnd: {\n      default: \"1.25rem\",\n      [mq.mobileGrid]: \"1.5625rem\",\n    },\n    zIndex: vlak.zSticky,\n    width: vlak.hit,\n    height: vlak.hit,\n    minWidth: vlak.hit,\n    minHeight: vlak.hit,\n    padding: \"0.625rem\",\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    overflow: \"hidden\",\n    backgroundColor: \"transparent\",\n    borderWidth: 0,\n    borderStyle: \"none\",\n    borderRadius: vlak.radiusSm,\n    cursor: \"pointer\",\n    color: {\n      default: vlak.gray,\n      \":hover\": { default: null, [mq.hover]: vlak.ink },\n    },\n    outlineWidth: {\n      default: 0,\n      \":focus-visible\": 2,\n    },\n    outlineStyle: {\n      default: \"none\",\n      \":focus-visible\": \"solid\",\n    },\n    outlineColor: {\n      default: \"transparent\",\n      \":focus-visible\": vlak.ink,\n    },\n    outlineOffset: {\n      default: 0,\n      \":focus-visible\": 2,\n    },\n    filter: `drop-shadow(0 0 12px ${vlak.paper}) drop-shadow(0 0 20px ${vlak.paper})`,\n  },\n  inline: {\n    position: \"relative\",\n    top: \"auto\",\n    insetInlineEnd: \"auto\",\n    zIndex: 1,\n    filter: \"none\",\n  },\n  mark: {\n    width: \"1.5rem\",\n    height: \"1.5rem\",\n    display: \"block\",\n    flexShrink: 0,\n  },\n});\n\n/**\n * One filled family mark on a 24px square, about 18–20px of drawn ink.\n * Moon on paper, sun on black. The button sets\n * data-theme=\"dark\" on <html> and persists the choice.\n * Apps pin it top-right; catalog and previews use the inline modifier.\n * The name states the action (\"Switch to dark scheme\"), so it changes with the state.\n */\nexport const ThemeToggle = React.forwardRef<HTMLButtonElement, ThemeToggleProps>(function ThemeToggle({\n  storageKey = \"vlak-theme\",\n  onThemeChange,\n  className,\n  style,\n  onClick,\n  ...props\n}, ref) {\n  const [dark, setDark] = React.useState<boolean | null>(null);\n\n  React.useEffect(() => {\n    const media = window.matchMedia?.(\"(prefers-color-scheme: dark)\");\n    const read = () => {\n      const explicit = document.documentElement.dataset.theme;\n      setDark(explicit === \"dark\" || (explicit !== \"light\" && !!media?.matches));\n    };\n    read();\n    const observer = new MutationObserver(read);\n    observer.observe(document.documentElement, { attributes: true, attributeFilter: [\"data-theme\"] });\n    media?.addEventListener(\"change\", read);\n    return () => {\n      observer.disconnect();\n      media?.removeEventListener(\"change\", read);\n    };\n  }, []);\n\n  const toggle = (e: React.MouseEvent<HTMLButtonElement>) => {\n    const explicit = document.documentElement.dataset.theme;\n    const effectiveDark = explicit === \"dark\" || (explicit !== \"light\" && !!window.matchMedia?.(\"(prefers-color-scheme: dark)\").matches);\n    const next = !effectiveDark;\n    document.documentElement.dataset.theme = next ? \"dark\" : \"light\";\n    try {\n      localStorage.setItem(storageKey, next ? \"dark\" : \"light\");\n    } catch {\n      /* private mode */\n    }\n    setDark(next);\n    onThemeChange?.(next);\n    onClick?.(e);\n  };\n\n  const inline = typeof className === \"string\" && className.split(/\\s+/).includes(\"rs-theme-toggle-inline\");\n  const sx = rs([\"rs-theme-toggle\", className], styles.toggle, inline && styles.inline);\n  const mark = rs([dark ? \"rs-theme-sun\" : \"rs-theme-moon\"], styles.mark);\n\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      aria-label={dark ? \"Switch to light scheme\" : \"Switch to dark scheme\"}\n      onClick={toggle}\n      {...props}\n      className={sx.className}\n      style={{ ...sx.style, ...style }}\n    >\n      <Icon name={dark ? \"sun\" : \"moon\"} variant=\"filled\" size={24} className={mark.className} style={mark.style} />\n    </button>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/theme-toggle.tsx"
    },
    {
      "path": "vlak/styles/theme-toggle.css",
      "content": "/* ── theme-toggle: generated from packages/react/src/components/theme-toggle.tsx ── */\n.rs-theme-toggle{transition:var(--transition);position:fixed;top:1.5rem;inset-inline-end:1.25rem;z-index:var(--z-sticky);width:var(--hit);height:var(--hit);min-width:var(--hit);min-height:var(--hit);padding:0.625rem;display:flex;align-items:center;justify-content:center;overflow:hidden;background-color:transparent;border-width:0;border-style:none;border-radius:var(--radius-sm);cursor:pointer;color:var(--text-secondary);outline-width:0;outline-style:none;outline-color:transparent;outline-offset:0;filter:drop-shadow(0 0 12px var(--bg)) drop-shadow(0 0 20px var(--bg))}\n.rs-theme-toggle:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:2px}\n@media (prefers-reduced-motion: reduce){.rs-theme-toggle{transition:none}}\n@media (max-width: 480px){.rs-theme-toggle{inset-inline-end:1.5625rem}}\n@media (hover: hover) and (pointer: fine){.rs-theme-toggle:hover{color:var(--text)}}\n.rs-theme-toggle-inline{position:relative;top:auto;inset-inline-end:auto;z-index:1;filter:none}\n.rs-theme-sun,.rs-theme-moon{width:1.5rem;height:1.5rem;display:block;flex-shrink:0}\n",
      "type": "registry:file",
      "target": "styles/vlak/theme-toggle.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "actions",
      "classes": [
        "rs-theme-toggle",
        "rs-theme-toggle-inline",
        "rs-theme-sun",
        "rs-theme-moon"
      ],
      "snippet": "<button type=\"button\" class=\"rs-theme-toggle rs-theme-toggle-inline\" aria-label=\"Switch to dark scheme\"><svg class=\"rs-icon rs-theme-moon\" aria-hidden=\"true\" viewBox=\"0 0 16 16\" width=\"24\" height=\"24\" fill=\"currentColor\" stroke=\"none\"><path d=\"M13.5 8.5 A5.5 5.5 0 1 1 7.5 2.5 A4.5 4.5 0 0 0 13.5 8.5 Z\"/></svg></button>",
      "cssOnly": false,
      "registryDependencies": [
        "icons",
        "vlak-lib"
      ],
      "aliases": [
        "Theme toggle",
        "Dark mode toggle",
        "Color scheme switch",
        "Mode toggle"
      ],
      "example": "import { ThemeToggle } from \"@noorddev/vlak-react\";\n\n<ThemeToggle storageKey=\"vlak-theme\" onThemeChange={(dark) => track(dark)} />",
      "usage": {
        "use": [
          "Letting the reader pick light or dark and remembering it.",
          "Top-right of the page chrome; rs-theme-toggle-inline for a spot in the flow."
        ],
        "avoid": [
          "Pages that should follow the system only; leave it out and the system preference applies."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Moves focus to the button"
        },
        {
          "keys": "Enter, Space",
          "does": "Switches the scheme"
        }
      ],
      "a11y": [
        "A native <button> whose aria-label states the action (\"Switch to dark scheme\" or \"Switch to light scheme\").",
        "The target mode is shown with the filled Vlak moon or sun, on a 24px icon square inside a 44px target.",
        "Sets an explicit data-theme=\"light\" or data-theme=\"dark\" on <html> and stores the choice under storageKey; read it early in your document to avoid a flash."
      ]
    }
  }
}
