{
  "generatedFrom": "button.contract.json",
  "component": "Button",
  "slug": "button",
  "group": "General",
  "tier": "leaf-lit",
  "summary": "Trigger an action or navigation — the primary interactive control.",
  "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-button';",
    "element": "aha-button",
    "registers": "aha-button"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/button/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/button/button.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/button.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/button.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-button> 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-button"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-button"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-button",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "variant",
      "type": "primary | secondary | tertiary | link | danger | success | positive | primary-alt | text | text-link",
      "default": "secondary",
      "desc": "tone × emphasis; one primary per view. success is a synonym of positive; primary-alt of primary; text / text-link are borderless label buttons"
    },
    {
      "name": "size",
      "type": "sm | md | lg",
      "default": "lg",
      "desc": "28 / 36 / 40px height — lg (40) is the default"
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "desc": "non-interactive; grey fill"
    },
    {
      "name": "loading",
      "type": "boolean",
      "default": "false",
      "desc": "shows a spinner, blocks clicks"
    },
    {
      "name": "block",
      "type": "boolean",
      "default": "false",
      "desc": "full-width"
    },
    {
      "name": "iconOnly",
      "type": "boolean (attr icon-only)",
      "default": "false",
      "desc": "square, label-less; requires aria-label"
    },
    {
      "name": "icon",
      "type": "slot",
      "default": "—",
      "desc": "leading glyph via <span slot=\"icon\">; for icon-only, the glyph is the default child"
    }
  ],
  "tokens": [
    "button-primary-bg",
    "button-primary-bg-hover",
    "button-primary-bg-press",
    "button-primary-text",
    "button-default-bg",
    "button-default-border",
    "button-danger-bg",
    "button-ghost-bg-hover",
    "button-positive-bg",
    "button-disabled-bg",
    "button-disabled-text",
    "button-focus-ring",
    "text-default",
    "text-link",
    "radius-xs",
    "radius-default",
    "focus"
  ],
  "spec": [
    {
      "label": "Sizes",
      "value": "sm 28 (r4) · md 36 (r8) · lg 40 (r8, default) — heights; fontSize 14 (sm/md) / 16 (lg)"
    },
    {
      "label": "Primary",
      "value": "bg #6A1EBB, label #FDFDFD, hover #8644D4, active #5715A0"
    },
    {
      "label": "Secondary",
      "value": "white bg, #1A1A1A label, 1px #E3E3E3 border; hover brand-tint #F9F5FF + #A96FF0 border"
    },
    {
      "label": "Tertiary / Link",
      "value": "transparent; tertiary hover #F9F5FF, active #F0E4FF; link #6A1EBB weight 400"
    },
    {
      "label": "Danger",
      "value": "bg #F5222D, white label, hover #FF4D4F, active #CF1322"
    },
    {
      "label": "Positive / Success",
      "value": "bg #4EF1C5, dark label #1A1A1A, hover #93F5DA, active #20E8B5 (success = alias of positive)"
    },
    {
      "label": "Primary-alt",
      "value": "same fill as primary #6A1EBB (a second primary slot for a two-primary layout)"
    },
    {
      "label": "Text / Text-link",
      "value": "white bg, no border, weight 400; text = secondary-grey label, hover grey fill; text-link = #6A1EBB label, hover #A96FF0"
    },
    {
      "label": "Disabled",
      "value": "bg #E3E3E3, label #B5B5B5"
    },
    {
      "label": "Label / icon",
      "value": "Plus Jakarta Sans SemiBold 600, 14 (sm/md) / 16 (lg/xl); icon 16px, inline-flex, 8px gap"
    },
    {
      "label": "Focus",
      "value": "soft box-shadow ring, spread 2 — #D3B4FF@30% (default) · danger #FF2850@20% · positive #20E8B5@30%"
    },
    {
      "label": "Elevation",
      "value": "subtle drop-shadow (primary rgba0,0,0,.04 · secondary .016)"
    },
    {
      "label": "Icon-only",
      "value": "square (width = height per size: 28/36/40/52), glyph centred, no label; needs aria-label"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "primary",
        "when": "the single most important action on a view (one per screen)"
      },
      {
        "what": "secondary",
        "when": "the default for most actions — neutral, white with a border"
      },
      {
        "what": "tertiary / link",
        "when": "low-emphasis or inline navigation, no container weight"
      },
      {
        "what": "danger",
        "when": "a destructive action (delete, remove) — pair with a confirm"
      },
      {
        "what": "positive",
        "when": "a confirming/success action where a positive signal helps"
      },
      {
        "what": "icon-only",
        "when": "a compact action where the glyph is self-explanatory (close, more, add) — always give an aria-label"
      }
    ],
    "note": "Size: LARGE (lg) is the default for most actions; step down to MEDIUM (md) or SMALL (sm) in denser/secondary regions to lower an action's information-hierarchy, not by whim. Size only via the size attribute — never inline height/padding/radius/fontSize. Primary fill is #6A1EBB; hover #8644D4 per DS V3.",
    "dsv3": "<aha-button> binds to a NAMED button semantic-token layer (--aha-button-*): --aha-button-primary-bg/-bg-hover/-bg-press/-text, --aha-button-default-bg/-text/-border/-bg-hover/-border-hover/-border-press, --aha-button-ghost-bg-hover/-bg-press (tertiary), --aha-button-danger-bg/-bg-hover/-bg-press/-text/-ring, --aha-button-positive-bg/-bg-hover/-bg-press/-text, --aha-button-disabled-bg/-text, --aha-button-focus-ring/-focus-ring-success, --aha-button-elevate-primary/-secondary. Each is a pure indirection whose value references the core DS token (e.g. --aha-button-primary-bg: var(--aha-color-primary)), so button theming can move independently of the core palette with NO visual change — the rendered colours are byte-identical to the raw --aha-btn-* seeds."
  },
  "surfaces": [
    "editor",
    "dashboard",
    "settings",
    "audience"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step, no bundler.\n     <aha-button> 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/aha-button.js';   // registers <aha-button>\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/icons.js';   // registers <aha-icon> for the icon slot\n</script>\n\n<div style=\"display:flex; gap:8px\">\n  <aha-button variant=\"secondary\">Cancel</aha-button>\n  <aha-button variant=\"primary\" onclick=\"alert('Saved')\">\n    <span slot=\"icon\"><aha-icon name=\"system-plus\" size=\"16\" decorative></aha-icon></span>\n    Save changes\n  </aha-button>\n</div>\n\n<!-- Size + variant are attributes; never inline height/padding/radius/fontSize. -->\n<aha-button variant=\"primary\" size=\"lg\" block>Get started</aha-button>\n<aha-button icon-only variant=\"tertiary\" aria-label=\"Close\"><aha-icon name=\"system-x\" size=\"16\" decorative></aha-icon></aha-button>\n\n<!-- Low-emphasis label buttons: text / text-link are borderless. primary-alt is a second primary slot. -->\n<aha-button variant=\"text\">Skip</aha-button>\n<aha-button variant=\"text-link\">Learn more</aha-button>\n",
    "react": "import '@ahaslides-product/design/aha-button';   // registers <aha-button>\nimport '@ahaslides-product/design/icons';   // registers <aha-icon> for the icon slot\nimport { useRef } from 'react';\n\n// React 19 can use <aha-button> directly; React 18 uses this thin wrapper so onClick + slots work.\nfunction AhaButton({ variant = 'secondary', size = 'md', disabled, loading, iconOnly, icon, children, ...rest }) {\n  const ref = useRef();\n  return (\n    <aha-button\n      ref={ref}\n      variant={variant}\n      size={size}\n      {...(disabled ? { disabled: true } : {})}\n      {...(loading ? { loading: true } : {})}\n      {...(iconOnly ? { 'icon-only': true } : {})}\n      {...rest}\n    >\n      {icon ? <span slot=\"icon\">{icon}</span> : null}\n      {children}\n    </aha-button>\n  );\n}\n\nexport function SaveBar({ onSave, saving }) {\n  return (\n    <div style={{ display: 'flex', gap: 8 }}>\n      <AhaButton variant=\"secondary\">Cancel</AhaButton>\n      <AhaButton variant=\"primary\" loading={saving} onClick={onSave}\n        icon={<aha-icon name=\"system-plus\" size={16} decorative />}>\n        Save changes\n      </AhaButton>\n    </div>\n  );\n}\n",
    "vue": "<!-- Vue 3 consumes the same <aha-button> custom element natively.\n     Tell the compiler it's a custom element (vite: compilerOptions.isCustomElement = t => t.startsWith('aha-')). -->\n<script setup>\nimport '@ahaslides-product/design/aha-button';   // registers <aha-button>\nimport '@ahaslides-product/design/icons';   // registers <aha-icon> for the icon slot\ndefineProps({ saving: Boolean });\nconst emit = defineEmits(['save']);\n</script>\n\n<template>\n  <div style=\"display:flex; gap:8px\">\n    <aha-button variant=\"secondary\">Cancel</aha-button>\n    <aha-button variant=\"primary\" :loading=\"saving\" @click=\"emit('save')\">\n      <span slot=\"icon\">\n        <aha-icon name=\"system-plus\" :size=\"16\" decorative />\n      </span>\n      Save changes\n    </aha-button>\n  </div>\n</template>\n"
  }
}
