{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "combobox",
  "type": "registry:ui",
  "title": "Combobox",
  "description": "A text input that filters a list. Built on `@base-ui/react/combobox`: Base UI owns the filtering (`Intl.Collator`-backed `useFilter`), the combobox/listbox ARIA pairing, VIRTUAL focus (the input keeps DOM focus and `aria-activedescendant` moves), single +…",
  "author": "ofri-peretz <https://github.com/ofri-peretz>",
  "categories": [
    "form",
    "primitive"
  ],
  "dependencies": [
    "@base-ui/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://ds.interlace.tools/r/theme.json",
    "https://ds.interlace.tools/r/cn.json"
  ],
  "files": [
    {
      "path": "registry/interlace-ui/combobox.tsx",
      "target": "components/ui/combobox.tsx",
      "type": "registry:ui",
      "content": "'use client';\n\nimport * as React from 'react';\n\n// @interlace/combobox v1.0.0 — Interlace design system.\n// Docs, props and live preview: https://ds.interlace.tools/c/combobox\n// What changed since: https://ds.interlace.tools/c/combobox#history\n// Generated banner — keep it, the upgrade diff reads this version.\n\n/**\n * @interlace/ui — Combobox\n *\n * A text input that filters a list. Built on `@base-ui/react/combobox`: Base UI\n * owns the filtering (`Intl.Collator`-backed `useFilter`), the\n * combobox/listbox ARIA pairing, VIRTUAL focus (the input keeps DOM focus and\n * `aria-activedescendant` moves), single + multiple selection, chips, and the\n * whole keyboard model. We own the surface — size, tokens, focus ring, target\n * sizes.\n * Mirrors: https://ui.shadcn.com/docs/components/combobox\n *\n * ## Combobox vs Select vs DropdownMenu\n *\n * `Select` is the right primitive when the user picks from a list they can\n * read at a glance and can NOT type into. `Combobox` is for the list that is\n * too long to scan — the user types to narrow it. `DropdownMenu` is for\n * ACTIONS, not values. Reaching for a Combobox under ~7 options is a\n * downgrade: it trades a one-keystroke listbox for a text field.\n *\n * ## Anatomy\n *\n *   Combobox (Root)                          (no DOM — state + filtering)\n *     ├─ ComboboxChips                       (multiple only)\n *     │   └─ ComboboxChip → ComboboxChipRemove\n *     ├─ ComboboxControl                     (ours — relative row, no Base UI part)\n *     │   ├─ ComboboxInput                   (input — data-min-viewport=320)\n *     │   ├─ ComboboxClear                   (button, keepMounted=false)\n *     │   └─ ComboboxTrigger → ComboboxIcon  (optional open button)\n *     └─ ComboboxContent                     (portal → positioner → popup)\n *         ├─ ComboboxEmpty                   (renders only when 0 results)\n *         ├─ ComboboxStatus                  (polite live region — async lists)\n *         └─ ComboboxList\n *             ├─ ComboboxGroup → ComboboxGroupLabel\n *             ├─ ComboboxItem                (repeats)\n *             └─ ComboboxSeparator\n *\n * ## Filtering — the one trap in this API\n *\n * Base UI filters through `ComboboxCollection`, which `ComboboxList` wraps\n * implicitly when its child is a FUNCTION. Both of these render; only the\n * first one filters:\n *\n *   <ComboboxList>{(item) => <ComboboxItem value={item}>…</ComboboxItem>}</ComboboxList>   ✅\n *   <ComboboxList>{items.map((item) => <ComboboxItem …/>)}</ComboboxList>                  ❌\n *\n * The mapped form is not an error and looks correct at rest — it just shows\n * every row no matter what is typed, which reads as \"the filter is broken\"\n * rather than as \"the wrong child shape\". It also needs `items` on the root:\n * without it there is no source list to filter, and `ComboboxEmpty` can never\n * know the list is empty rather than merely unmounted.\n *\n * Static rows that must never be filtered (a \"Create new…\" affordance) are\n * the legitimate use of the mapped form, alongside a function child.\n *\n * ## Keyboard contract\n *\n * Read from the installed Base UI source (`combobox/input/ComboboxInput.js`,\n * `combobox/root/AriaCombobox.js`), not from the shadcn/Radix model — three\n * rows below differ from `Select` and getting them wrong is how a combobox\n * ships an unusable text field. Asserted in `Combobox.stories.tsx`\n * (`KeyboardFlow`) and locked by `combobox-keyboard-lock.test.ts`.\n *\n * | Key             | Action                                                   |\n * | --------------- | -------------------------------------------------------- |\n * | a–z / any glyph | Filter the list; the popup opens on the first keystroke   |\n * | ↓ / ↑           | Move the highlight; opens the popup when closed. Wraps    |\n * |                 | (`loopFocus: true`) — last ↓ returns to the first item     |\n * | Enter           | Commit the highlighted item; closes the popup             |\n * | Esc (open)      | Close the popup. Focus never moved, so nothing to restore |\n * | Esc (closed)    | Clear the input AND the selection — NOT a no-op           |\n * | Home / End      | Move the TEXT CARET. They do NOT jump to first/last item; |\n * |                 | Base UI stops the event on the input. This is the         |\n * |                 | opposite of `Select`, where Home/End rove the listbox     |\n * | Backspace       | With an empty input in `multiple` mode, removes the last  |\n * |                 | chip                                                      |\n * | Tab             | Leaves the field. The popup is not a tab stop — items are |\n * |                 | reached with arrows, never with Tab                       |\n *\n * FOCUS NEVER ENTERS THE LIST. `useListNavigation` runs with `virtual: true`,\n * so `document.activeElement` stays the input for the entire interaction and\n * the highlight is published as `aria-activedescendant`. A test that asserts\n * `activeElement.role === 'option'` (the correct assertion for `Select`) is\n * asserting a bug here.\n *\n * ## MIN_VIEWPORT — 320\n *\n * The input is `w-full` and the popup is width-anchored to it\n * (`min-w-(--anchor-width)`) and height-capped by `max-h-(--available-height)`,\n * so it scrolls rather than growing past a 320px viewport. Every hit target\n * (item 36px, clear 28px, chip remove 24px) clears the 24px floor of\n * SC 2.5.8 at that width.\n *\n * ## Contrast (verified by token math)\n *\n * Identical token pairs on identical surfaces to `select.tsx`; the measured\n * figures are carried over from `interlace-theme.css`.\n *\n * | Composite                                            | Light   | Dark    | Floor           |\n * | ---------------------------------------------------- | ------- | ------- | --------------- |\n * | input `border-input` on `--background`               | 3.62:1  | 3.35:1  | 3:1 (SC 1.4.11) |\n * | `focus-visible:ring-ring/60` on `--background`       | 3.23:1  | 4.73:1  | 3:1 (SC 2.4.13) |\n * | `placeholder:text-muted-foreground` on `--background`| 9.41:1  | 8.74:1  | 4.5:1 (SC 1.4.3)|\n * | item `text-popover-foreground` on `--popover`        | 19.65:1 | 15.71:1 | 4.5:1 (SC 1.4.3)|\n * | highlighted `text-accent-foreground` on `--accent`   | 8.98:1  | 10.46:1 | 4.5:1 (SC 1.4.3)|\n * | selected `text-primary-foreground` on `--primary`    | 8.80:1  | 11.79:1 | 4.5:1 (SC 1.4.3)|\n * | group label `text-muted-foreground` on `--popover`   | 9.41:1  | 10.48:1 | 4.5:1 (SC 1.4.3)|\n *\n * The focus ring is 3px (`ring-[3px]`), over the 2px minimum of SC 2.4.13, and\n * `/60` rather than `/50` because 50%-alpha primary composites to 2.57:1 on\n * white — under the 3:1 indicator floor.\n *\n * `data-disabled:opacity-50` is exempt — SC 1.4.3 carves out inactive\n * user-interface components.\n *\n * | Rule | Concept                          | Where in this file                                          |\n * | ---- | -------------------------------- | ----------------------------------------------------------- |\n * | R4   | Extends Base UI part props       | each wrapper extends its Base UI part's props               |\n * | R6   | data-slot per part               | `combobox-input` / `-control` / `-popup` / `-item` / …      |\n * | R7   | className merged + ...rest       | `cn(BASE, className)` + `{...props}` on every part          |\n * | R8   | No `isXxx` prefix on booleans    | `multiple`, `disabled`, `readOnly` — inherited, no `is`     |\n * | R11  | Composition over prop-drilling   | items are children; `ComboboxCompose` is the escape hatch   |\n * | R12  | Reuse over wrap                  | Base UI owns filtering, ARIA, virtual focus, chips          |\n * | R14  | Controlled + uncontrolled        | `value`/`onValueChange` + `defaultValue`, ditto `inputValue`|\n * | R17  | API parity with shadcn           | part names mirror shadcn's Command/Combobox surface         |\n * | R18  | Tailwind only                    | zero inline `style`                                         |\n * | R19  | Tokens only                      | semantic colour tokens throughout — no raw hex              |\n * | R20  | AA contrast                      | table above — every composite measured                      |\n * | R25  | Client component                 | required — Base UI Combobox ships client hooks              |\n * | R26  | A11y from upstream               | combobox/listbox ARIA + the full keyboard model             |\n */\n\nimport { Combobox as BaseCombobox } from '@base-ui/react/combobox';\nimport { CheckIcon, ChevronDownIcon, XIcon } from 'lucide-react';\n\nimport { cn } from '@/lib/utils';\n\n/** Smallest viable viewport (CSS px) for this primitive. */\nexport const MIN_VIEWPORT = 320 as const;\n\n/**\n * Root. Renders no DOM of its own, so it is aliased rather than wrapped —\n * wrapping would erase the `<Value, Multiple>` generics that make\n * `onValueChange` typed at the call site.\n */\nconst Combobox = BaseCombobox.Root;\nconst ComboboxValue = BaseCombobox.Value;\nconst ComboboxPortal = BaseCombobox.Portal;\n/** Renders the filtered items from `items` — for the closed-template API. */\nconst ComboboxCollection = BaseCombobox.Collection;\n\n/**\n * The input row. Ours, not a Base UI part: Base UI has no concept of \"the\n * input plus the affordances docked inside its box\", and a `relative` wrapper\n * is the only way `ComboboxClear` / `ComboboxTrigger` can sit inside the\n * field's border instead of beside it.\n */\nfunction ComboboxControl({ className, ...props }: React.ComponentProps<'div'>) {\n  return (\n    <div\n      data-slot=\"combobox-control\"\n      className={cn('relative flex w-full items-center', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxInput({\n  className,\n  size = 'default',\n  ...props\n}: Omit<React.ComponentProps<typeof BaseCombobox.Input>, 'size'> & {\n  /**\n   * Control height. Shadows the native `size` attribute (character width),\n   * which no design system call-site wants and which would intersect to\n   * `never` if both were kept. Destructured, never spread — a literal\n   * `size=\"sm\"` on an `<input>` is invalid HTML.\n   */\n  size?: 'sm' | 'default';\n}) {\n  return (\n    <BaseCombobox.Input\n      data-slot=\"combobox-input\"\n      data-min-viewport={String(MIN_VIEWPORT)}\n      data-size={size}\n      className={cn(\n        'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none md:text-sm',\n        'data-[size=default]:h-9 data-[size=sm]:h-8',\n        // /60 not /50 — 50%-alpha primary composites to 2.57:1 on white,\n        // under the 3:1 focus-indicator floor (SC 2.4.13). 3px > the 2px\n        // minimum thickness the same SC requires.\n        'focus-visible:border-ring focus-visible:ring-ring/60 focus-visible:ring-[3px]',\n        'aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',\n        'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * Optional button that opens the popup without typing. Docked inside the\n * field, so the input reserves room for it with `pr-9` at the call site.\n * 28px square — over the 24px floor of SC 2.5.8.\n */\nfunction ComboboxTrigger({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Trigger>) {\n  return (\n    <BaseCombobox.Trigger\n      data-slot=\"combobox-trigger\"\n      className={cn(\n        'text-muted-foreground hover:text-foreground absolute end-1 flex size-7 items-center justify-center rounded-sm transition-colors outline-none',\n        'focus-visible:ring-ring/60 focus-visible:ring-[3px]',\n        'disabled:pointer-events-none disabled:opacity-50',\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? (\n        <BaseCombobox.Icon data-slot=\"combobox-icon\">\n          <ChevronDownIcon />\n        </BaseCombobox.Icon>\n      )}\n    </BaseCombobox.Trigger>\n  );\n}\n\nfunction ComboboxIcon({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Icon>) {\n  return (\n    <BaseCombobox.Icon\n      data-slot=\"combobox-icon\"\n      className={cn('flex items-center', className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * Clears the value. Unmounted until there IS one, so it never occupies a tab\n * stop that does nothing.\n *\n * \"Value\" means the SELECTED item, not the typed query — a half-typed filter\n * with no selection shows no clear button, because Escape already clears the\n * query and a second affordance for it would be noise. Set `keepMounted` if\n * a stable layout matters more than the empty tab stop.\n */\nfunction ComboboxClear({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Clear>) {\n  return (\n    <BaseCombobox.Clear\n      data-slot=\"combobox-clear\"\n      className={cn(\n        'text-muted-foreground hover:text-foreground absolute end-1 flex size-7 items-center justify-center rounded-sm transition-colors outline-none',\n        // Step aside when a trigger follows. Both affordances dock at\n        // `end-1`, so a call site that renders BOTH — which the\n        // compositional API permits and `ComboboxCompose` happens never\n        // to do — stacked them invisibly on top of each other. No error,\n        // no console warning, just one button covering another.\n        // `:has(~ …)` keeps the fix in CSS and out of the public API:\n        // neither part needs to know whether the other was rendered.\n        '[&:has(~[data-slot=combobox-trigger])]:end-9',\n        'focus-visible:ring-ring/60 focus-visible:ring-[3px]',\n        'disabled:pointer-events-none disabled:opacity-50',\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <XIcon />\n          <span className=\"sr-only\">Clear</span>\n        </>\n      )}\n    </BaseCombobox.Clear>\n  );\n}\n\nfunction ComboboxContent({\n  className,\n  children,\n  sideOffset = 4,\n  popupClassName,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Positioner> & {\n  popupClassName?: string;\n}) {\n  return (\n    <BaseCombobox.Portal>\n      <BaseCombobox.Positioner\n        data-slot=\"combobox-positioner\"\n        sideOffset={sideOffset}\n        className={cn('z-50', className)}\n        {...props}\n      >\n        <BaseCombobox.Popup\n          data-slot=\"combobox-popup\"\n          data-min-viewport={String(MIN_VIEWPORT)}\n          className={cn(\n            'border-border bg-popover text-popover-foreground max-h-(--available-height) min-w-(--anchor-width) overflow-x-hidden overflow-y-auto rounded-xl border p-1 shadow-lg',\n            'origin-(--transform-origin)',\n            'data-starting-style:scale-95 data-starting-style:opacity-0',\n            'data-ending-style:scale-95 data-ending-style:opacity-0',\n            'transition-[opacity,scale] duration-150',\n            popupClassName,\n          )}\n        >\n          {children}\n        </BaseCombobox.Popup>\n      </BaseCombobox.Positioner>\n    </BaseCombobox.Portal>\n  );\n}\n\nfunction ComboboxList({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.List>) {\n  return (\n    <BaseCombobox.List\n      data-slot=\"combobox-list\"\n      className={cn('flex flex-col outline-none', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxItem({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Item>) {\n  return (\n    <BaseCombobox.Item\n      data-slot=\"combobox-item\"\n      className={cn(\n        // 36px tall (py-2 + text-sm line box) — over the 24px floor of\n        // SC 2.5.8 at the 320px viewport.\n        'text-popover-foreground relative flex w-full cursor-pointer items-center gap-2 rounded-lg py-2 pr-8 pl-3 text-sm transition-colors duration-150 outline-hidden select-none',\n        'data-highlighted:bg-accent data-highlighted:text-accent-foreground',\n        'data-selected:bg-primary data-selected:text-primary-foreground data-selected:font-medium',\n        'data-disabled:pointer-events-none data-disabled:opacity-50',\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <span className=\"absolute end-2 flex size-3.5 items-center justify-center\">\n        <BaseCombobox.ItemIndicator data-slot=\"combobox-item-indicator\">\n          <CheckIcon className=\"size-4\" />\n        </BaseCombobox.ItemIndicator>\n      </span>\n    </BaseCombobox.Item>\n  );\n}\n\nfunction ComboboxGroup({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Group>) {\n  return (\n    <BaseCombobox.Group\n      data-slot=\"combobox-group\"\n      className={cn('flex flex-col', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxGroupLabel({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.GroupLabel>) {\n  return (\n    <BaseCombobox.GroupLabel\n      data-slot=\"combobox-group-label\"\n      className={cn(\n        'text-muted-foreground px-3 py-1.5 text-xs font-medium',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * Renders only when the filtered list is empty, and announces itself\n * politely. Requires `items` on the root — without it Base UI cannot know\n * the list is empty rather than merely unmounted.\n *\n * `empty:py-0` is not cosmetic. Base UI keeps this element mounted at all\n * times — it is a `role=\"status\" aria-live=\"polite\"` region, and a live\n * region has to exist BEFORE its content changes or the change is never\n * announced. It only drops its children. So without the collapse, `py-6`\n * reserves a 48px dead band above every non-empty result list, and the\n * popup looks broken with no element to blame it on. Measured in Chromium,\n * not guessed: jsdom reports every box as 0×0.\n *\n * `empty:hidden` would also close the gap and is what several other\n * libraries do — it is the wrong fix, because `display: none` takes the\n * region out of the accessibility tree, and a live region that appears in\n * the same frame as its content is a live region screen readers skip.\n */\nfunction ComboboxEmpty({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Empty>) {\n  return (\n    <BaseCombobox.Empty\n      data-slot=\"combobox-empty\"\n      className={cn(\n        'text-muted-foreground px-3 py-6 text-center text-sm empty:py-0',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * A polite live region for async list state (\"Loading…\", \"12 results\"). Not\n * the same thing as `ComboboxEmpty`: this one is always mounted, so a change\n * of its text is announced.\n */\nfunction ComboboxStatus({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Status>) {\n  return (\n    <BaseCombobox.Status\n      data-slot=\"combobox-status\"\n      className={cn('text-muted-foreground px-3 py-2 text-xs', className)}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxSeparator({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Separator>) {\n  return (\n    <BaseCombobox.Separator\n      data-slot=\"combobox-separator\"\n      className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}\n      {...props}\n    />\n  );\n}\n\n/**\n * The chip rail for `multiple` — wraps the chips AND the input.\n *\n * Give the nested input a floor (`min-w-24 flex-1`), not `flex-1` alone: a\n * flex child with `min-w-0` shrinks to nothing before the row wraps, so with\n * two chips in the rail the text field collapses to ~40px and the\n * placeholder is a single clipped letter. Verified in Chromium — jsdom has\n * no layout and reports it as fine.\n *\n * Only the border changes on focus, with no `ring-*`: the input inside the\n * rail already carries the DS-wide `:focus-visible` outline from\n * `styles/preflight.css`, and stacking a container ring on top of it is two\n * indicators for one focus.\n */\nfunction ComboboxChips({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Chips>) {\n  return (\n    <BaseCombobox.Chips\n      data-slot=\"combobox-chips\"\n      className={cn(\n        'border-input dark:bg-input/30 flex w-full flex-wrap items-center gap-1.5 rounded-md border bg-transparent p-1.5 shadow-xs',\n        'focus-within:border-ring',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxChip({\n  className,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.Chip>) {\n  return (\n    <BaseCombobox.Chip\n      data-slot=\"combobox-chip\"\n      className={cn(\n        'bg-secondary text-secondary-foreground flex h-7 items-center gap-1 rounded-md pr-1 pl-2 text-sm outline-none',\n        'data-highlighted:bg-accent data-highlighted:text-accent-foreground',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nfunction ComboboxChipRemove({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof BaseCombobox.ChipRemove>) {\n  return (\n    <BaseCombobox.ChipRemove\n      data-slot=\"combobox-chip-remove\"\n      className={cn(\n        // 24px square — exactly the SC 2.5.8 floor. Do not shrink it to\n        // match the icon; the icon is 12px and the target is not the icon.\n        'flex size-6 items-center justify-center rounded-sm transition-colors outline-none',\n        'hover:bg-accent hover:text-accent-foreground',\n        'focus-visible:ring-ring/60 focus-visible:ring-[3px]',\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? <XIcon />}\n    </BaseCombobox.ChipRemove>\n  );\n}\n\n/* ─────────────────────────────────────────────────────────────────\n * ComboboxCompose — convenience composition.\n *\n * The single-select \"type to filter a flat list\" case, which is ~80% of\n * combobox call-sites, in one element. Anything else — groups, chips,\n * per-row icons, async status — composes the parts above.\n *\n *   <ComboboxCompose\n *     items={plugins}\n *     placeholder=\"Search plugins…\"\n *     emptyMessage=\"No plugin matches.\"\n *     onValueChange={setPlugin}\n *   />\n *\n * `items` is `{ value, label }[]` because Base UI resolves exactly that\n * shape to a display string with no `itemToStringLabel` prop.\n * ──────────────────────────────────────────────────────────────── */\ninterface ComboboxComposeItem {\n  value: string;\n  label: string;\n  disabled?: boolean;\n}\n\ninterface ComboboxComposeProps {\n  /** The full, unfiltered list. Base UI filters it against the query. */\n  items: readonly ComboboxComposeItem[];\n  /** Placeholder for the text input. */\n  placeholder?: string;\n  /** Shown when the query matches nothing. */\n  emptyMessage?: React.ReactNode;\n  /** Accessible name for the input. Required — the field has no visible label. */\n  'aria-label'?: string;\n  /** Initially selected item. Uncontrolled. */\n  defaultValue?: ComboboxComposeItem | null;\n  /** Selected item. Controlled — pair with `onValueChange`. */\n  value?: ComboboxComposeItem | null;\n  /** Fired with the newly selected item, or `null` when cleared. */\n  onValueChange?: (value: ComboboxComposeItem | null) => void;\n  /** Form field name — projected onto the hidden input. */\n  name?: string;\n  /** Ignore all interaction. */\n  disabled?: boolean;\n  /** Show a clear button once the field has a value. */\n  clearable?: boolean;\n  /** Override the control className (width). */\n  className?: string;\n}\n\nfunction ComboboxCompose({\n  items,\n  placeholder,\n  emptyMessage = 'No results found.',\n  'aria-label': ariaLabel,\n  defaultValue,\n  value,\n  onValueChange,\n  name,\n  disabled,\n  clearable = true,\n  className,\n}: ComboboxComposeProps) {\n  return (\n    <Combobox\n      items={items}\n      defaultValue={defaultValue}\n      value={value}\n      onValueChange={onValueChange}\n      name={name}\n      disabled={disabled}\n    >\n      <ComboboxControl className={className}>\n        <ComboboxInput\n          placeholder={placeholder}\n          aria-label={ariaLabel}\n          className={clearable ? 'pr-9' : undefined}\n        />\n        {clearable ? <ComboboxClear /> : null}\n      </ComboboxControl>\n      <ComboboxContent>\n        <ComboboxEmpty>{emptyMessage}</ComboboxEmpty>\n        {/* Function child, NOT `items.map(...)` — see the Filtering note in\n            the file header. A mapped list renders every row forever. */}\n        <ComboboxList>\n          {(item: ComboboxComposeItem) => (\n            <ComboboxItem key={item.value} value={item} disabled={item.disabled}>\n              {item.label}\n            </ComboboxItem>\n          )}\n        </ComboboxList>\n      </ComboboxContent>\n    </Combobox>\n  );\n}\n\nexport {\n  Combobox,\n  ComboboxChip,\n  ComboboxChipRemove,\n  ComboboxChips,\n  ComboboxClear,\n  ComboboxCollection,\n  ComboboxCompose,\n  ComboboxContent,\n  ComboboxControl,\n  ComboboxEmpty,\n  ComboboxGroup,\n  ComboboxGroupLabel,\n  ComboboxIcon,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxList,\n  ComboboxPortal,\n  ComboboxSeparator,\n  ComboboxStatus,\n  ComboboxTrigger,\n  ComboboxValue,\n};\nexport type { ComboboxComposeItem, ComboboxComposeProps };\n"
    }
  ],
  "meta": {
    "tier": "primitive",
    "client": true,
    "minViewport": 320,
    "loading": false,
    "version": "1.0.0",
    "since": "1.1.0"
  },
  "docs": "## @interlace/combobox\n\nInstalled to `components/ui/combobox.tsx`.\n\n```tsx\nimport { /* … */ } from '@/components/ui/combobox';\n```\n\nProps, a11y contract, live preview and source: https://ds.interlace.tools/c/combobox\n\nRequires the `@interlace/theme` CSS baseline (installed automatically as a registry dependency)."
}
