{
  "generatedFrom": "input.contract.json",
  "component": "Input",
  "slug": "input",
  "group": "Data Entry",
  "tier": "leaf-lit",
  "summary": "A single-line text field — names, search, numbers, short free text — with sizes, error/warning status, affixes, a clear ✕ and a password reveal.",
  "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-input';",
    "element": "aha-input",
    "registers": "aha-input"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/input/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/input/input.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/input.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/input.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-input> 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-input"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-input"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-input",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "value",
      "type": "string",
      "default": "\"\"",
      "desc": "Controlled text value"
    },
    {
      "name": "placeholder",
      "type": "string",
      "default": "—",
      "desc": "Hint shown when empty"
    },
    {
      "name": "type",
      "type": "text | number | email | password | search",
      "default": "text",
      "desc": "Native input type; password adds a reveal (eye) toggle"
    },
    {
      "name": "size",
      "type": "small | default | large",
      "default": "default",
      "desc": "Control height — small 24 · default 32 · large 40 (DS V3 field scale); radius stays 8 (sm/md/lg accepted as aliases)"
    },
    {
      "name": "status",
      "type": "default | error | warning",
      "default": "default",
      "desc": "Border + ring recolour; error also sets aria-invalid (legacy `invalid` boolean = status error)"
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "desc": "Non-interactive; grey fill"
    },
    {
      "name": "readonly",
      "type": "boolean",
      "default": "false",
      "desc": "Selectable but not editable; secondary fill"
    },
    {
      "name": "prefix-icon / prefix",
      "type": "string",
      "default": "—",
      "desc": "Leading affix — an icon by name (from the DS library) or plain text"
    },
    {
      "name": "suffix-icon / suffix",
      "type": "string",
      "default": "—",
      "desc": "Trailing affix — an icon by name or plain text (e.g. a domain)"
    },
    {
      "name": "clearable",
      "type": "boolean",
      "default": "false",
      "desc": "Shows a ✕ when non-empty that wipes the value and emits `clear`"
    },
    {
      "name": "input",
      "type": "CustomEvent<{value}>",
      "default": "—",
      "desc": "Composed event on each keystroke; read e.detail.value"
    },
    {
      "name": "clear",
      "type": "CustomEvent<{value}>",
      "default": "—",
      "desc": "Composed event when the ✕ clears the field"
    }
  ],
  "tokens": [
    "border",
    "border-hover",
    "color-primary",
    "bg-container",
    "bg-container-secondary",
    "bg-container-disabled",
    "bg-hover",
    "text-default",
    "text-tertiary",
    "text-disabled",
    "radius-default",
    "radius-sm",
    "focus-ring-soft",
    "border-error",
    "color-error",
    "border-warning",
    "color-warning",
    "border-disabled"
  ],
  "spec": [
    {
      "label": "Box",
      "value": "height 32 (default) · radius 8 · padding 0 12"
    },
    {
      "label": "Sizes",
      "value": "small 24 · default 32 · large 40 — all radius 8"
    },
    {
      "label": "Rest border",
      "value": "1px #E3E3E3"
    },
    {
      "label": "Hover border",
      "value": "#D3B4FF (purple-30)"
    },
    {
      "label": "Focus",
      "value": "#6A1EBB border + 2px #D3B4FF@30% ring (on the persistent wrapper)"
    },
    {
      "label": "Error",
      "value": "#F5222D border + error-tinted ring; sets aria-invalid"
    },
    {
      "label": "Warning",
      "value": "#FF7747 border + warning-tinted ring"
    },
    {
      "label": "Disabled",
      "value": "#F1F1F1 fill, #EBEBEB border, not-allowed"
    },
    {
      "label": "Readonly",
      "value": "#F7F7F7 fill, still selectable"
    },
    {
      "label": "Affixes",
      "value": "prefix/suffix icon (by name) or text; clearable ✕; password reveal (eye)"
    },
    {
      "label": "Text",
      "value": "Plus Jakarta 14/21; placeholder #8A8A8A"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "Input",
        "when": "short single-line free text — a name, a search term, a number"
      },
      {
        "what": "Textarea",
        "when": "multi-line free text (descriptions, messages)"
      },
      {
        "what": "Select",
        "when": "one value from a known set — don't make the user type it"
      }
    ],
    "note": "Label the field with a noun phrase; use placeholder for an example, never as the label. Show the error status only after the user leaves the field, and pair it with a message that says how to fix it. Reach for a prefix icon to hint the field's job (a magnifier for search, an envelope for email), a suffix for a fixed unit or domain, clearable for search/filter fields, and password reveal for credentials.",
    "dsv3": "COVERED — enumerated from the DS V3 Input component-set taxonomy: Input (Size · Status), Components/Input-Affix (prefix/suffix), Input-Addon/Label + Input-Addon/Icon (addons), Password (reveal toggle). Sizes match the measured DS V3 input cells (small/default/large = 24/32/40, radius 8, default 32 = controlHeight root). Error border is the measured error stroke bound to `border-error` (#F5222D token; the measured cell reads #FF5A7A, reconciled to the DS error token). Rest border is bound to the `border` token (#E3E3E3): the measured cell reads #D4D4D4, but the reconciled token layer resolves the field border to the export brand gray #E3E3E3 (precedence EXPORT for brand colour), kept consistent across the whole field family (input/select/form/datepicker). Warning status + affixes/clear/reveal follow the same token layer; motion + focus ring on a persistent node."
  },
  "surfaces": [
    "editor",
    "dashboard",
    "settings",
    "audience"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step.\n     <aha-input> 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     Icons are summoned by name from the DS icon library via <aha-icon>. -->\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> (affix glyphs)\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-input.js';   // registers <aha-input>\n</script>\n\n<!-- size: small | default | large   ·   status: error | warning   ·   disabled / readonly -->\n<aha-input id=\"ws\" size=\"large\" placeholder=\"Workspace name\" value=\"AhaSlides\"></aha-input>\n\n<!-- affixes: a leading prefix icon, a trailing suffix text, a clear ✕, a password reveal -->\n<aha-input prefix-icon=\"system-magnifying-glass\" type=\"search\" clearable placeholder=\"Search slides\"></aha-input>\n<aha-input suffix=\".ahaslides.com\" value=\"myteam\"></aha-input>\n<aha-input type=\"password\" prefix-icon=\"system-lock\" value=\"secret123\"></aha-input>\n<aha-input status=\"error\" value=\"not-an-email\" placeholder=\"Email\"></aha-input>\n\n<script>\n  // Plain DOM — no framework. Read e.detail.value off the composed CustomEvent.\n  document.getElementById('ws').addEventListener('input', (e) => {\n    console.log('value:', e.detail.value);\n  });\n</script>\n",
    "react": "import '@ahaslides-product/design/icons';     // registers <aha-icon> (affix glyphs)\nimport '@ahaslides-product/design/aha-input'; // registers <aha-input>\nimport { useRef, useEffect } from 'react';\n\n// React 18 needs a thin wrapper; React 19 can use <aha-input> directly.\n// size: 'small' | 'default' | 'large'  ·  status: 'error' | 'warning'\nfunction AhaInput({ value, placeholder, type, size, status, disabled, readOnly,\n                    prefixIcon, suffix, clearable, onChange }) {\n  const ref = useRef();\n  useEffect(() => {\n    const el = ref.current;\n    el.value = value ?? '';\n    el.disabled = !!disabled;\n    const h = (e) => onChange?.(e.detail.value);   // composed CustomEvent\n    el.addEventListener('input', h);\n    return () => el.removeEventListener('input', h);\n  });\n  return (\n    <aha-input\n      ref={ref}\n      placeholder={placeholder}\n      type={type}\n      {...(size ? { size } : {})}\n      {...(status ? { status } : {})}\n      {...(readOnly ? { readonly: '' } : {})}\n      {...(prefixIcon ? { 'prefix-icon': prefixIcon } : {})}\n      {...(suffix ? { suffix } : {})}\n      {...(clearable ? { clearable: '' } : {})}\n    />\n  );\n}\n\n// usage\n<AhaInput value={email} placeholder=\"Email\" status=\"error\"\n          prefixIcon=\"system-envelope\" clearable onChange={setEmail} />\n",
    "vue": "// main.ts — register the elements + mark aha-* as custom elements\nimport '@ahaslides-product/design/icons';       // registers <aha-icon> (affix glyphs)\nimport '@ahaslides-product/design/aha-input';   // registers <aha-input>\napp.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');\n\n// Component.vue\n<script setup>\nimport { ref } from 'vue';\nconst email = ref('');\n</script>\n\n<template>\n  <!-- size: small | default | large  ·  status: error | warning  ·  prefix-icon / suffix / clearable -->\n  <aha-input\n    size=\"large\"\n    status=\"error\"\n    prefix-icon=\"system-envelope\"\n    clearable\n    placeholder=\"Email\"\n    :value.prop=\"email\"\n    @input=\"email = $event.detail.value\"\n    @clear=\"email = ''\"\n  />\n</template>\n"
  }
}
