{
  "generatedFrom": "switch.contract.json",
  "component": "Switch",
  "slug": "switch",
  "group": "Data Entry",
  "tier": "leaf-lit",
  "summary": "A single setting that takes effect immediately — on or off.",
  "install": {
    "package": "@ahaslides-product/design",
    "registry": "https://npm.pkg.github.com",
    "scope": "@ahaslides-product",
    "auth": "Published to GitHub Packages — needs a GitHub token with read:packages. Configure the @ahaslides-product scope in .npmrc before installing.",
    "npmrc": "@ahaslides-product:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}   # a GitHub token with read:packages",
    "command": "npm i @ahaslides-product/design",
    "tokenLayer": "import '@ahaslides-product/design/tokens.css';",
    "import": "import '@ahaslides-product/design/aha-switch';",
    "element": "aha-switch",
    "registers": "aha-switch"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/switch/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/switch/switch.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/switch.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/switch.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-switch> is a standard custom element that renders on open — React/Vue are thin adapters over the same element. Paste into an .html file and it renders with no build step.",
  "frameworks": {
    "react": {
      "via": "web-component",
      "ref": "aha-switch"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-switch"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-switch",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "checked",
      "type": "boolean",
      "default": "false",
      "desc": "On/off state (reflected)"
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "desc": "Non-interactive"
    },
    {
      "name": "loading",
      "type": "boolean",
      "default": "false",
      "desc": "Spinner in the knob; blocks toggle while busy (aria-busy)"
    },
    {
      "name": "size",
      "type": "'default' | 'small'",
      "default": "default",
      "desc": "Track 44×22 (default) or 28×16 (small)"
    },
    {
      "name": "on-text",
      "type": "string",
      "default": "—",
      "desc": "Text shown inside the track when on"
    },
    {
      "name": "off-text",
      "type": "string",
      "default": "—",
      "desc": "Text shown inside the track when off"
    },
    {
      "name": "on-icon",
      "type": "icon name",
      "default": "—",
      "desc": "DS icon shown inside the track when on (wins over on-text)"
    },
    {
      "name": "off-icon",
      "type": "icon name",
      "default": "—",
      "desc": "DS icon shown inside the track when off (wins over off-text)"
    },
    {
      "name": "change",
      "type": "CustomEvent<{checked}>",
      "default": "—",
      "desc": "Composed event on toggle; read e.detail.checked"
    }
  ],
  "tokens": [
    "gray-20",
    "gray-50",
    "color-primary",
    "white",
    "text-tertiary",
    "radius-pill",
    "motion-slow"
  ],
  "spec": [
    {
      "label": "Track",
      "value": "44×22 (small 28×16) · radius pill · 1px #D4D4D4 border"
    },
    {
      "label": "Knob",
      "value": "18×18 white circle, slides 20px (small 12×12, slides 12px)"
    },
    {
      "label": "Off",
      "value": "track #F7F7F7 (gray-20)"
    },
    {
      "label": "On",
      "value": "track #6A1EBB (primary)"
    },
    {
      "label": "Disabled",
      "value": "40% opacity, not-allowed"
    },
    {
      "label": "Loading",
      "value": "system-circle-notch spinner rides in the knob; interaction blocked"
    },
    {
      "label": "Children",
      "value": "optional text or icon inside the track, away from the knob (checked ↔ unchecked)"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "Switch",
        "when": "a single setting that applies immediately — no save step"
      },
      {
        "what": "Checkbox",
        "when": "options saved together as a group, or expressing consent"
      },
      {
        "what": "Radio",
        "when": "exactly one from a small mutually-exclusive set"
      }
    ],
    "note": "Never mix switches and checkboxes in one group. The label names what the setting controls as a noun phrase; the switch communicates the action — don't prefix the label with Enable/Turn on. Reach for `loading` on an async setting that may fail, `size=small` in a dense list, and checked/unchecked children only when the on/off meaning isn't obvious from context.",
    "dsv3": "COVERED — enumerated from the DS V3 Switch component-set + AntD matrix: size (default | small), disabled, loading (spinner in the knob), and checkedChildren/unCheckedChildren (text or icon inside the track). The default-size look is matched to the measured DS V3 `toggle` cell: track 44×22, radius pill, 1px #D4D4D4 border, off #F7F7F7 (gray-20), on #6A1EBB, disabled 40% opacity (small = 28×16 knob-12, from the AntD scale). All bound to canonical gray/primary/text-tertiary tokens; the knob slide + track fill + spinner animate on persistent nodes via the shared motion tokens (checked/loading toggle a host attr, never a rebuild — the Switch-click trap)."
  },
  "surfaces": [
    "editor",
    "dashboard",
    "settings",
    "audience"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step.\n     <aha-switch> is the SAME shared custom element React and Vue consume — here in its\n     native form. Theming comes only from the --aha-* tokens in tokens.css. -->\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css\">\n<script type=\"module\">\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js';       // registers <aha-icon> (loading spinner + children glyphs)\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-switch.js';   // registers <aha-switch>\n</script>\n\n<label style=\"display:inline-flex; align-items:center; gap:8px; font-family:'Plus Jakarta Sans',sans-serif; font-size:14px\">\n  <aha-switch id=\"anon\" checked></aha-switch>\n  Show results live\n</label>\n\n<!-- size=\"small\" · loading (spinner in the knob) · checked/unchecked children (text or an icon in the track) -->\n<div style=\"display:flex; gap:16px; align-items:center; margin-top:12px\">\n  <aha-switch size=\"small\" checked></aha-switch>\n  <aha-switch loading checked></aha-switch>\n  <aha-switch checked on-text=\"On\" off-text=\"Off\"></aha-switch>\n  <aha-switch checked on-icon=\"system-check\" off-icon=\"system-x\"></aha-switch>\n</div>\n\n<script>\n  document.getElementById('anon').addEventListener('change', (e) => {\n    console.log('live results:', e.detail.checked);\n  });\n</script>\n",
    "react": "import '@ahaslides-product/design/icons';        // registers <aha-icon> (loading spinner + children glyphs)\nimport '@ahaslides-product/design/aha-switch';   // registers <aha-switch>\nimport { useRef, useEffect } from 'react';\n\n// React 18 needs a thin wrapper; React 19 can use <aha-switch> directly.\nfunction AhaSwitch({ checked, disabled, loading, size, checkedChildren, unCheckedChildren, onChange }) {\n  const ref = useRef();\n  useEffect(() => {\n    const el = ref.current;\n    el.checked = !!checked;\n    el.disabled = !!disabled;\n    el.loading = !!loading;\n    size ? el.setAttribute('size', size) : el.removeAttribute('size');\n    checkedChildren != null ? el.setAttribute('on-text', checkedChildren) : el.removeAttribute('on-text');\n    unCheckedChildren != null ? el.setAttribute('off-text', unCheckedChildren) : el.removeAttribute('off-text');\n    const h = (e) => onChange?.(e.detail.checked);   // composed CustomEvent\n    el.addEventListener('change', h);\n    return () => el.removeEventListener('change', h);\n  });\n  return <aha-switch ref={ref} />;\n}\n\n// usage — applies immediately, no save step\n<AhaSwitch checked={live} onChange={setLive} />\n<AhaSwitch size=\"small\" checked={compact} onChange={setCompact} />\n<AhaSwitch loading checked={saving} />\n<AhaSwitch checkedChildren=\"On\" unCheckedChildren=\"Off\" checked={live} onChange={setLive} />\n",
    "vue": "// main.ts — register the elements + mark aha-* as custom elements\nimport '@ahaslides-product/design/icons';        // registers <aha-icon> (loading spinner + children glyphs)\nimport '@ahaslides-product/design/aha-switch';\napp.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');\n\n// Component.vue\n<script setup>\nimport { ref } from 'vue';\nconst live = ref(true);\nconst saving = ref(false);\n</script>\n\n<template>\n  <aha-switch\n    :checked.prop=\"live\"\n    @change=\"live = $event.detail.checked\"\n  />\n  <!-- size=\"small\" · loading · checked/unchecked children (text or icon) -->\n  <aha-switch size=\"small\" :checked.prop=\"live\" @change=\"live = $event.detail.checked\" />\n  <aha-switch :loading.prop=\"saving\" :checked.prop=\"live\" />\n  <aha-switch on-text=\"On\" off-text=\"Off\" :checked.prop=\"live\" @change=\"live = $event.detail.checked\" />\n</template>\n"
  }
}
