{
  "generatedFrom": "card-select.contract.json",
  "component": "Card select",
  "slug": "card-select",
  "group": "Settings",
  "tier": "leaf-lit",
  "summary": "Single-select as an icon+label card grid — pick one of N options each shown as its own visual card, with roving radiogroup keyboard navigation. Two layouts: labelled tiles (stretch to fill `columns`) or compact 44px icon-only squares.",
  "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-card-select';",
    "element": "aha-card-select",
    "registers": "aha-card-select"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/card-select/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/card-select/card-select.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/card-select.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/card-select.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-card-select> 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-card-select"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-card-select"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-card-select",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "options",
      "type": "Array<{value,label,icon}>",
      "default": "[]",
      "desc": "The cards — each an icon name + a label; set as a property or a JSON attribute"
    },
    {
      "name": "value",
      "type": "string",
      "default": "—",
      "desc": "The selected option's value (single-select)"
    },
    {
      "name": "columns",
      "type": "number",
      "default": "3",
      "desc": "Grid column count — labelled tiles fill each track; iconOnly lays out that many 44px squares"
    },
    {
      "name": "iconOnly",
      "type": "boolean",
      "default": "false",
      "desc": "Compact layout — fixed 44px icon squares packed to the start; the label is hidden but kept as title + aria-label"
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "desc": "Non-interactive; grey fill"
    },
    {
      "name": "change",
      "type": "CustomEvent<{value}>",
      "default": "—",
      "desc": "Composed event on selection; read e.detail.value"
    }
  ],
  "tokens": [
    "border-strong",
    "border-hover",
    "border-disabled",
    "color-primary",
    "bg-container",
    "bg-container-disabled",
    "bg-accent",
    "text-default",
    "text-disabled",
    "icon-default",
    "radius-default"
  ],
  "spec": [
    {
      "label": "Card",
      "value": "icon 24 over label; padding 16 12; radius 8; 1px #D4D4D4 border"
    },
    {
      "label": "Grid",
      "value": "repeat(columns, minmax(0,1fr)) — tiles stretch to fill; columns default 3; 8px gap"
    },
    {
      "label": "iconOnly",
      "value": "repeat(columns, 44px) — fixed 44px squares packed to the start; label hidden, kept as title + aria-label"
    },
    {
      "label": "Selected",
      "value": "#6A1EBB border + #F9F5FF tint + #6A1EBB label/icon (persistent node)"
    },
    {
      "label": "Hover",
      "value": "#D3B4FF border"
    },
    {
      "label": "Keyboard",
      "value": "roving radiogroup — one tab-stop; Arrow keys move-and-select; Space/Enter select"
    },
    {
      "label": "Disabled",
      "value": "#F1F1F1 fill, #EBEBEB border, not-allowed"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "Card select",
        "when": "pick one of N options, each carrying a visual (icon/preview) — a chart-type, layout or mode picker"
      },
      {
        "what": "Segmented",
        "when": "2–4 short text-only options in one row (SETTINGS-38)"
      },
      {
        "what": "Select",
        "when": "a longer or space-constrained set with no per-option visual"
      }
    ],
    "note": "Reach for CardSelect only when each option carries a visual. Do not hand-roll a grid of bespoke <button>/<div> tiles with inline <svg> icons and a manual selected state (SETTINGS-51) — the roving radiogroup a11y is the hard part and it lives here.",
    "dsv3": "COVERED — the settings-lab CardSelect control mapped into the DS. The card, tint and brand-border selection reuse the DS V3 radio-card family; the icon+label grid and its roving radiogroup keyboard model are the settings-specific shape (SETTINGS-51)."
  },
  "surfaces": [
    "editor",
    "settings"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step.\n     <aha-card-select> is the SAME shared custom element React and Vue consume — here in its native\n     form. Theming comes only from the --aha-* tokens in tokens.css. The whole grid is one radiogroup:\n     Tab in, then Arrow keys move-and-select. Icons are summoned by name — never an inline <svg>. -->\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-card-select.js';   // registers <aha-card-select>\n</script>\n\n<!-- labelled tiles — stretch to fill each of `columns` tracks -->\n<aha-card-select id=\"chart\" columns=\"3\" value=\"bar\" style=\"max-width:360px\"></aha-card-select>\n\n<!-- iconOnly — compact 44px squares packed to the start; the label is kept as title + aria-label -->\n<aha-card-select id=\"chartCompact\" icon-only columns=\"3\" value=\"bar\"></aha-card-select>\n\n<script type=\"module\">\n  // options: value + label + an icon NAME from the DS library\n  const options = [\n    { value: 'bar',   label: 'Bar',   icon: 'system-chart-bar' },\n    { value: 'line',  label: 'Line',  icon: 'system-chart-line-up' },\n    { value: 'pie',   label: 'Pie',   icon: 'system-chart-pie' },\n  ];\n  const el = document.getElementById('chart');\n  el.options = options;\n  el.addEventListener('change', (e) => console.log('chart type:', e.detail.value));\n\n  document.getElementById('chartCompact').options = options;\n</script>\n",
    "react": "import '@ahaslides-product/design/aha-card-select'; // registers <aha-card-select>\nimport { useRef, useEffect } from 'react';\n\n// React 18 needs a thin wrapper; React 19 can use <aha-card-select> directly.\n// options is a PROPERTY (array), so set it imperatively; the grid is one roving radiogroup.\nfunction AhaCardSelect({ options, value, columns, iconOnly, disabled, onChange }) {\n  const ref = useRef();\n  useEffect(() => {\n    const el = ref.current;\n    el.options = options || [];\n    if (value != null) el.value = value;\n    el.iconOnly = !!iconOnly;   // compact 44px squares; label kept as title + aria-label\n    el.disabled = !!disabled;\n    const h = (e) => onChange?.(e.detail.value);   // composed CustomEvent\n    el.addEventListener('change', h);\n    return () => el.removeEventListener('change', h);\n  }, [options, value, iconOnly, disabled, onChange]);\n  return <aha-card-select ref={ref} {...(columns ? { columns: String(columns) } : {})} />;\n}\n\n// usage — labelled tiles (default) or compact iconOnly squares\n<AhaCardSelect\n  value={chartType}\n  columns={3}\n  iconOnly={false}\n  options={[\n    { value: 'bar',  label: 'Bar',  icon: 'system-chart-bar' },\n    { value: 'line', label: 'Line', icon: 'system-chart-line-up' },\n    { value: 'pie',  label: 'Pie',  icon: 'system-chart-pie' },\n  ]}\n  onChange={setChartType}\n/>\n",
    "vue": "// main.ts — register the element + mark aha-* as custom elements\nimport '@ahaslides-product/design/aha-card-select';   // registers <aha-card-select>\napp.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');\n\n// Component.vue\n<script setup>\nimport { ref } from 'vue';\nconst chartType = ref('bar');\nconst options = [\n  { value: 'bar',  label: 'Bar',  icon: 'system-chart-bar' },\n  { value: 'line', label: 'Line', icon: 'system-chart-line-up' },\n  { value: 'pie',  label: 'Pie',  icon: 'system-chart-pie' },\n];\n</script>\n\n<template>\n  <!-- options + value are bound as PROPERTIES (.prop); the grid is one roving radiogroup -->\n  <!-- add icon-only for compact 44px squares (label kept as title + aria-label) -->\n  <aha-card-select\n    columns=\"3\"\n    :options.prop=\"options\"\n    :value.prop=\"chartType\"\n    @change=\"chartType = $event.detail.value\"\n  />\n</template>\n"
  }
}
