{
  "generatedFrom": "select-field.contract.json",
  "component": "Select field",
  "slug": "select-field",
  "group": "Data Entry",
  "tier": "leaf-lit",
  "summary": "Pick one value from a short, known set — a lightweight native-select-backed field that embeds anywhere.",
  "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-select';",
    "element": "aha-select",
    "registers": "aha-select"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/select-field/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/select-field/select-field.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/select-field.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/select-field.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-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-select"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-select"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-select",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "options",
      "type": "{ label, value }[]",
      "default": "[]",
      "desc": "The selectable set — a JSON `options` attribute or an `.options` property"
    },
    {
      "name": "value",
      "type": "string",
      "default": "—",
      "desc": "Selected value (controlled)"
    },
    {
      "name": "placeholder",
      "type": "string",
      "default": "—",
      "desc": "Dimmed hint shown when no value is selected"
    },
    {
      "name": "size",
      "type": "small | default | large",
      "default": "default",
      "desc": "Control height 24 / 32 / 40"
    },
    {
      "name": "status",
      "type": "error | warning",
      "default": "—",
      "desc": "Validation border colour"
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "desc": "Non-interactive, muted control"
    },
    {
      "name": "change",
      "type": "CustomEvent<{value}>",
      "default": "—",
      "desc": "Composed event; read e.detail.value"
    },
    {
      "name": "open",
      "type": "boolean",
      "default": "false",
      "desc": "Reflects/controls the themed listbox popup being open (also toggled by click/keyboard). aria-expanded stays in sync."
    }
  ],
  "tokens": [
    "color-primary",
    "border",
    "border-hover",
    "border-error",
    "border-warning",
    "bg-container",
    "bg-container-disabled",
    "border-disabled",
    "text-default",
    "text-tertiary",
    "text-disabled",
    "font-product"
  ],
  "spec": [
    {
      "label": "Control",
      "value": "height 32 · radius 8 · 1px #E3E3E3 border"
    },
    {
      "label": "Size",
      "value": "small 24 · default 32 · large 40"
    },
    {
      "label": "Focus",
      "value": "brand #6A1EBB border + soft ring"
    },
    {
      "label": "Placeholder",
      "value": "#8A8A8A"
    },
    {
      "label": "Caret",
      "value": "trailing aha-icon system-caret-down (UA arrow suppressed)"
    },
    {
      "label": "Status",
      "value": "error #F5222D · warning #FF7747 border"
    },
    {
      "label": "Popup",
      "value": "themed role=listbox — white surface, radius 8, elevation shadow, brand-tint hover, check on selected; Arrow/Home/End + typeahead + Escape; outside-click close"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "Select field (this leaf)",
        "when": "one value from a short, known set (≤ ~10), or a select inside a shadow-DOM surface like a settings row — no search/tags/multi needed"
      },
      {
        "what": "Select (composite)",
        "when": "a long list needing type-to-search, multiple/tags, async loading, or option groups"
      },
      {
        "what": "Radio",
        "when": "2–5 mutually-exclusive options all worth showing at once"
      }
    ],
    "note": "Pre-select the recommended default rather than leaving it empty. Keep option labels short and sentence case; keep product nouns — Q&A, Word cloud. This is the leaf half of the hybrid — reach for the composite Select the moment you need search, tags, or a virtualised list.",
    "dsv3": "COVERED — the DS V3 select chrome measured against the Select component set on fileKey MwjvUjVI0HnfwD9EwPXRAy: control 32 · radius 8 · 1px border #E3E3E3, size axis small 24 / default 32 / large 40, brand #6A1EBB focus border, #8A8A8A placeholder, status error #F5222D / warning #FF7747. This is the native-<select>-backed LEAF form of that chrome (the composite Select carries the popup/search/tags axes). Token layer: white surface, border #E3E3E3, hover border-hover #D3B4FF, disabled bg-container-disabled #F1F1F1 / border-disabled #EBEBEB. a11y: a native <select> carries the full OS keyboard + AT contract for free — no custom listbox/roving to hand-roll; the border/focus transition lives on a PERSISTENT wrapper (a `_focused` attribute toggles; the <option> list repopulates in place, the transition node is never rebuilt). The trailing caret is summoned by name via <aha-icon name=\"system-caret-down\">, never an inline glyph."
  },
  "surfaces": [
    "editor",
    "dashboard",
    "settings",
    "audience"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step.\n     <aha-select> is the SAME shared custom element React and Vue consume. It's a styled control\n     backed by a NATIVE <select>, so it inherits the OS keyboard model + a11y for free. Pass the\n     `options` as JSON and an optional `placeholder`; it emits a composed `change` event. -->\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';       // <aha-icon> — the trailing caret\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-select.js';  // registers <aha-select>\n</script>\n\n<aha-select id=\"theme\" placeholder=\"Pick a theme\"\n  options='[{\"label\":\"Light\",\"value\":\"light\"},{\"label\":\"Dark\",\"value\":\"dark\"},{\"label\":\"High contrast\",\"value\":\"contrast\"}]'></aha-select>\n\n<script>\n  // Plain DOM — no framework. Read e.detail.value off the composed CustomEvent.\n  document.getElementById('theme').addEventListener('change', (e) => {\n    console.log('theme:', e.detail.value);\n  });\n</script>\n",
    "react": "import '@ahaslides-product/design/aha-select';   // registers <aha-select>\nimport '@ahaslides-product/design/icons';        // <aha-icon> — the trailing caret\nimport { useRef, useEffect } from 'react';\n\n// React 18 needs a thin wrapper (options is a property, not a string attribute); React 19 can use\n// <aha-select> directly. Same element, same shadow-DOM CSS → byte-identical with Vue.\nfunction AhaSelect({ value, options, placeholder, size, status, disabled, onChange }) {\n  const ref = useRef();\n  useEffect(() => {\n    const el = ref.current;\n    el.options = options || [];              // set the array as a property\n    if (value != null) el.value = value;\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  });\n  return (\n    <aha-select ref={ref} placeholder={placeholder} size={size} status={status} />\n  );\n}\n\n// usage\n<AhaSelect\n  placeholder=\"Pick a theme\"\n  value={theme}\n  options={[\n    { label: 'Light', value: 'light' },\n    { label: 'Dark', value: 'dark' },\n  ]}\n  onChange={setTheme}\n/>\n",
    "vue": "// main.ts — register the element + mark aha-* as custom elements\nimport '@ahaslides-product/design/aha-select';   // registers <aha-select>\nimport '@ahaslides-product/design/icons';        // <aha-icon> — the trailing caret\napp.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');\n\n// Component.vue\n<script setup>\nimport { ref } from 'vue';\nconst theme = ref('light');\nconst options = [\n  { label: 'Light', value: 'light' },\n  { label: 'Dark', value: 'dark' },\n];\n</script>\n\n<template>\n  <aha-select\n    placeholder=\"Pick a theme\"\n    :value=\"theme\"\n    :options.prop=\"options\"\n    @change=\"theme = $event.detail.value\"\n  ></aha-select>\n</template>\n"
  }
}
