# Combobox

> Search and select one or more values from a predefined list.

Source: https://www.honestui.com/docs/components/combobox

```tsx
"use client"

import {
  Combobox,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
} from "@/components/honest-ui/ui/combobox"

const items = [
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Orange", value: "orange" },
  { label: "Grape", value: "grape" },
  { label: "Strawberry", value: "strawberry" },
  { label: "Mango", value: "mango" },
  { label: "Pineapple", value: "pineapple" },
  { label: "Kiwi", value: "kiwi" },
  { label: "Peach", value: "peach" },
  { label: "Pear", value: "pear" },
]

export function ComboboxDemo() {
  return (
    <div className="w-full max-w-64">
      <Combobox items={items}>
        <ComboboxInput
          placeholder="Select an item…"
          aria-label="Select an item"
        />
        <ComboboxPopup>
          <ComboboxEmpty>No items found.</ComboboxEmpty>
          <ComboboxList>
            {(item) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxPopup>
      </Combobox>
    </div>
  )
}

```

## Overview [#overview]

Combobox is a select whose list you can search. Type to filter, pick one value — or several, rendered as removable chips — from a list that can run into the hundreds.

Where it sits among the value controls:

* **Short closed list** — Select or Radio Group; opening a filterable popup for six options adds steps, not clarity.
* **Long or growing list** — Combobox. This component.
* **Input that may not exist on any list** — Autocomplete (free text with suggestions) or Tags Input (collect many free values).
* **Running commands** — Command. A combobox commits values; it never performs actions.

## Anatomy [#anatomy]

The field combines an input (which both filters and displays the selection), a popup of filtered options, and in multiple mode a chip per selected value with its own remove control. Optional parts cover everything real lists need: `ComboboxGroup`/`ComboboxGroupLabel` for structure, `ComboboxEmpty` for zero matches, `ComboboxStatus` for result counts, `ComboboxClear` for one-click reset, and `ComboboxTrigger` when the field should open like a select rather than type-first.

## Behavior [#behavior]

**Keyboard.**

| Key                                       | Result                                            |
| ----------------------------------------- | ------------------------------------------------- |
| Character keys                            | Type to filter the options                        |
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Move through matching options                     |
| <kbd>Enter</kbd>                          | Choose the highlighted option                     |
| <kbd>Escape</kbd>                         | Close the popup (and clear the filter text first) |
| <kbd>Backspace</kbd>                      | In multiple mode, removes the last chip           |

Filtering matches against option labels as you type. When nothing matches, `ComboboxEmpty` states that plainly — an empty popup with no message reads as breakage.

**Multiple mode.** Set `multiple` and every choice becomes a chip instead of replacing the input's text. Chips are individually removable without reopening the popup, and the input keeps filtering for the next pick. Selections survive further searching because they are held by reference, not by what the input currently shows.

**Result status.** With async data, `ComboboxStatus` reports how many options matched — the difference between "3 results" and a silently shorter list.

## States [#states]

**Disabled** fields skip focus and submission. **Invalid** state follows `aria-invalid` with a danger border, announced to assistive technology; pair it with a FieldError explaining what to do. While options load asynchronously, keep the previous list visible and show loading via `ComboboxStatus` rather than blanking the popup — a flash of "no matches" during fetch teaches people their data is gone.

Long labels wrap inside the popup; chips truncate with ellipsis and expose their full label through `aria-label`. Colors come from tokens; layout mirrors in RTL because the popup anchors logically.

## Accessibility [#accessibility]

The input carries its accessible name from your visible label. The filtered list is a proper listbox: screen readers announce option count as you type, which option is highlighted, and each chip's name for removal ("Remove Riya Patel"). Nothing depends on color or position — selected options are marked by state, not styling alone.

Typeahead plus count announcements make large lists workable non-visually, but option copy still matters: put distinguishing words first ("Invoice INV-2042", not "INV-2042 — an invoice").

## Installation [#installation]


  

  
    <CliBlock commands="[&#x22;combobox&#x22;]" />
  

  
    
      
        Install the following dependencies:
      

      ```bash
      npm install @base-ui-components/react
      ```

      
        Copy and paste the following code into your project.
      

      ### components/ui/combobox.tsx

```tsx
"use client"

import * as React from "react"
import { Combobox as ComboboxPrimitive } from "@base-ui-components/react/combobox"
import { ChevronsUpDown as ChevronsUpDownIcon, X as XIcon } from "honestui/icons"

import { cn } from "@/lib/utils"
import { Input } from "@/components/honest-ui/ui/input"
import { ScrollArea } from "@/components/honest-ui/ui/scroll-area"

const ComboboxContext = React.createContext<{
  chipsRef: React.RefObject<HTMLDivElement | null> | null
  multiple: boolean
}>({
  chipsRef: null,
  multiple: false,
})

function Combobox<
  ItemValue,
  Multiple extends boolean | undefined = false,
>(props: ComboboxPrimitive.Root.Props<ItemValue, Multiple>) {
  const chipsRef = React.useRef<HTMLDivElement | null>(null)
  return (
    <ComboboxContext.Provider value={{ chipsRef, multiple: !!props.multiple }}>
      <ComboboxPrimitive.Root {...props} />
    </ComboboxContext.Provider>
  )
}

function ComboboxInput({
  className,
  showTrigger = true,
  showClear = false,
  size,
  ...props
}: Omit<ComboboxPrimitive.Input.Props, "size"> & {
  showTrigger?: boolean
  showClear?: boolean
  size?: "sm" | "default" | "lg" | number
}) {
  const { multiple } = React.useContext(ComboboxContext)
  const sizeValue = (size ?? "default") as "sm" | "default" | "lg" | number

  // multiple mode
  if (multiple) {
    return (
      <ComboboxPrimitive.Input
        data-slot="combobox-input"
        className={cn(
          "min-w-12 flex-1 text-base/5 outline-none sm:text-sm [[data-slot=combobox-chip]+&]:ps-0.5",
          sizeValue === "sm" ? "ps-1.5" : "ps-2",
          className
        )}
        data-size={typeof sizeValue === "string" ? sizeValue : undefined}
        size={typeof sizeValue === "number" ? sizeValue : undefined}
        {...props}
      />
    )
  }
  // single mode
  return (
    <div className="relative w-full has-disabled:opacity-64">
      <ComboboxPrimitive.Input
        data-slot="combobox-input"
        className={cn(
          sizeValue === "sm"
            ? "has-[+[data-slot=combobox-trigger],+[data-slot=combobox-clear]]:*:data-[slot=combobox-input]:pe-6.5"
            : "has-[+[data-slot=combobox-trigger],+[data-slot=combobox-clear]]:*:data-[slot=combobox-input]:pe-7",
          className
        )}
        render={<Input size={sizeValue} className="has-disabled:opacity-100" />}
        {...props}
      />
      {showTrigger && (
        <ComboboxTrigger
          className={cn(
            "absolute top-1/2 inline-flex size-7 shrink-0 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-72 transition-opacity outline-none hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
            sizeValue === "sm" ? "end-0" : "end-0.5"
          )}
        >
          <ChevronsUpDownIcon />
        </ComboboxTrigger>
      )}
      {showClear && (
        <ComboboxClear
          className={cn(
            "absolute top-1/2 inline-flex size-7 shrink-0 -translate-y-1/2 cursor-pointer items-center justify-center rounded-md border border-transparent opacity-72 transition-opacity outline-none hover:opacity-100 has-[+[data-slot=combobox-clear]]:hidden pointer-coarse:after:absolute pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
            sizeValue === "sm" ? "end-0" : "end-0.5"
          )}
        >
          <XIcon />
        </ComboboxClear>
      )}
    </div>
  )
}

function ComboboxTrigger({
  className,
  ...props
}: ComboboxPrimitive.Trigger.Props) {
  return (
    <ComboboxPrimitive.Trigger
      data-slot="combobox-trigger"
      className={className}
      {...props}
    />
  )
}

function ComboboxPopup({
  className,
  children,
  sideOffset = 4,
  ...props
}: ComboboxPrimitive.Popup.Props & {
  sideOffset?: number
}) {
  const { chipsRef } = React.useContext(ComboboxContext)

  return (
    <ComboboxPrimitive.Portal>
      <ComboboxPrimitive.Positioner
        data-slot="combobox-positioner"
        className="z-[var(--hui-z-index-portal)] select-none"
        sideOffset={sideOffset}
        anchor={chipsRef}
      >
        <span className="relative flex max-h-full">
          <ComboboxPrimitive.Popup
            data-slot="combobox-popup"
            className={cn(
              "box-border flex max-h-[min(var(--available-height),320px)] min-w-(--anchor-width) max-w-(--available-width) origin-(--transform-origin) flex-col overflow-auto rounded-[var(--hui-radius-2)] border-[0.5px] border-[var(--hui-color-border-base-primary)] bg-[var(--hui-color-background-base-primary)] shadow-[var(--hui-shadow-soft)] [font-size:var(--hui-font-size-small)] [letter-spacing:var(--hui-letter-spacing-small)] [line-height:var(--hui-line-height-small)] motion-safe:[transition:opacity_var(--hui-duration-fast)_var(--hui-ease-out),transform_var(--hui-duration-fast)_var(--hui-ease-out)] data-ending-style:scale-[0.97] data-ending-style:opacity-0 data-starting-style:scale-[0.97] data-starting-style:opacity-0 [&:has([data-slot=combobox-list]:empty)]:border-0",
              className
            )}
            {...props}
          >
            {children}
          </ComboboxPrimitive.Popup>
        </span>
      </ComboboxPrimitive.Positioner>
    </ComboboxPrimitive.Portal>
  )
}

function ComboboxItem({
  className,
  children,
  ...props
}: ComboboxPrimitive.Item.Props) {
  return (
    <ComboboxPrimitive.Item
      data-slot="combobox-item"
      className={cn(
        "relative flex items-center gap-[var(--hui-space-3)] whitespace-normal break-words rounded-[var(--hui-radius-2)] p-[var(--hui-space-3)] text-[var(--hui-color-foreground-base-primary)] outline-none in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:cursor-pointer data-highlighted:bg-[var(--hui-color-background-base-primary-hover)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--hui-space-5)]",
        className
      )}
      {...props}
    >
      <ComboboxPrimitive.ItemIndicator className="flex shrink-0 items-center justify-center [&_svg]:size-[var(--hui-space-5)]">
        <svg
          xmlns="http://www.w3.org/1500/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
        >
          <path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
        </svg>
      </ComboboxPrimitive.ItemIndicator>
      <div className="min-w-0">{children}</div>
    </ComboboxPrimitive.Item>
  )
}

function ComboboxSeparator({
  className,
  ...props
}: ComboboxPrimitive.Separator.Props) {
  return (
    <ComboboxPrimitive.Separator
      className={cn(
        "mx-[calc(var(--hui-space-3)*-1)] my-[var(--hui-space-2)] h-px bg-[var(--hui-color-border-base-primary)] last:hidden",
        className
      )}
      data-slot="combobox-separator"
      {...props}
    />
  )
}

function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) {
  return (
    <ComboboxPrimitive.Group
      data-slot="combobox-group"
      className={className}
      {...props}
    />
  )
}

function ComboboxGroupLabel({
  className,
  ...props
}: ComboboxPrimitive.GroupLabel.Props) {
  return (
    <ComboboxPrimitive.GroupLabel
      className={cn(
        "px-[var(--hui-space-3)] py-[var(--hui-space-2)] [font-size:var(--hui-font-size-mini)] [font-weight:var(--hui-font-weight-medium)]",
        className
      )}
      data-slot="combobox-group-label"
      {...props}
    />
  )
}

function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) {
  return (
    <ComboboxPrimitive.Empty
      className={cn(
        "text-center text-sm text-muted-foreground not-empty:p-2",
        className
      )}
      data-slot="combobox-empty"
      {...props}
    />
  )
}

function ComboboxRow({ className, ...props }: ComboboxPrimitive.Row.Props) {
  return (
    <ComboboxPrimitive.Row
      data-slot="combobox-row"
      className={className}
      {...props}
    />
  )
}

function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) {
  return <ComboboxPrimitive.Value data-slot="combobox-value" {...props} />
}

function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) {
  return (
    <ScrollArea className="flex-1">
      <ComboboxPrimitive.List
        data-slot="combobox-list"
        className={cn(
          "p-[var(--hui-space-2)] empty:p-0 in-data-has-overflow-y:pe-3",
          className
        )}
        {...props}
      />
    </ScrollArea>
  )
}

function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) {
  return (
    <ComboboxPrimitive.Clear
      data-slot="combobox-clear"
      className={className}
      {...props}
    />
  )
}

function ComboboxStatus({
  className,
  ...props
}: ComboboxPrimitive.Status.Props) {
  return (
    <ComboboxPrimitive.Status
      data-slot="combobox-status"
      className={cn(
        "px-3 py-2 text-xs font-medium text-muted-foreground empty:m-0 empty:p-0",
        className
      )}
      {...props}
    />
  )
}

function ComboboxCollection(props: ComboboxPrimitive.Collection.Props) {
  return (
    <ComboboxPrimitive.Collection data-slot="combobox-collection" {...props} />
  )
}

function ComboboxChips({ className, ...props }: ComboboxPrimitive.Chips.Props) {
  const { chipsRef } = React.useContext(ComboboxContext)

  return (
    <ComboboxPrimitive.Chips
      ref={chipsRef}
      data-slot="combobox-chips"
      className={cn(
        "relative inline-flex min-h-8 w-full flex-wrap gap-1 rounded-lg border border-input bg-background bg-clip-padding p-[calc(--spacing(1)-1px)] text-base/5 shadow-xs ring-ring/24 transition-shadow outline-none *:min-h-6 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_1px_--theme(--color-black/4%)] focus-within:border-ring focus-within:ring-[3px] has-disabled:pointer-events-none has-disabled:opacity-64 has-aria-invalid:border-destructive/36 focus-within:has-aria-invalid:border-destructive/64 focus-within:has-aria-invalid:ring-destructive/16 has-data-[size=lg]:min-h-9 has-data-[size=lg]:*:min-h-7 has-data-[size=sm]:min-h-7 has-data-[size=sm]:*:min-h-5 sm:text-sm dark:not-in-data-[slot=group]:bg-clip-border dark:not-has-disabled:bg-input/32 dark:not-has-disabled:not-focus-within:not-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/8%)] dark:has-aria-invalid:ring-destructive/24 [&:has(:disabled,:focus-within,[aria-invalid])]:shadow-none",
        className
      )}
      {...props}
    />
  )
}

function ComboboxChip({ children, ...props }: ComboboxPrimitive.Chip.Props) {
  return (
    <ComboboxPrimitive.Chip
      data-slot="combobox-chip"
      className="flex items-center rounded-md bg-accent ps-2 text-xs font-medium text-accent-foreground outline-none"
      {...props}
    >
      {children}
      <ComboboxChipRemove />
    </ComboboxPrimitive.Chip>
  )
}

function ComboboxChipRemove(props: ComboboxPrimitive.ChipRemove.Props) {
  return (
    <ComboboxPrimitive.ChipRemove
      data-slot="combobox-chip-remove"
      className="h-full shrink-0 cursor-pointer px-1.5 opacity-72 hover:opacity-100 [&_svg:not([class*='size-'])]:size-3.5"
      aria-label="Remove"
      {...props}
    >
      <XIcon />
    </ComboboxPrimitive.ChipRemove>
  )
}

export {
  Combobox,
  ComboboxInput,
  ComboboxTrigger,
  ComboboxPopup,
  ComboboxItem,
  ComboboxSeparator,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxEmpty,
  ComboboxValue,
  ComboboxList,
  ComboboxClear,
  ComboboxStatus,
  ComboboxRow,
  ComboboxCollection,
  ComboboxChips,
  ComboboxChip,
}

```

      
        Update the import paths to match your project setup.
      
    
  


## Usage [#usage]

```tsx
import {
  Combobox,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
} from "@/components/ui/combobox";
```

```tsx
<Combobox items={teammates}>
  <ComboboxPopup>
    <ComboboxInput placeholder="Search teammates…" />
    <ComboboxList>
      {(item) => (
        <ComboboxItem key={item.value} value={item}>
          {item.label}
        </ComboboxItem>
      )}
    </ComboboxList>
    <ComboboxEmpty>No teammates match.</ComboboxEmpty>
  </ComboboxPopup>
</Combobox>
```

## Don't do this [#dont-do-this]

### A closed set behind an open-texture control [#a-closed-set-behind-an-open-texture-control]

```tsx
// Bad
<Combobox items={[{ label: "Small" }, { label: "Medium" }, { label: "Large" }]} />
```

```tsx
// Good
<Select items={sizes} />
```

When the whole list fits under a thumb, search is ceremony: people must type to see what they could have scanned instantly. Filtering pays off only when scanning costs more than typing — dozens of options, or ones people already know by name.

### Silently empty while loading [#silently-empty-while-loading]

```tsx
// Bad
<ComboboxPopup>
  {loading ? null : <ComboboxList>…</ComboboxList>}
  {/* shows an empty popup mid-fetch */}
</ComboboxPopup>
```

```tsx
// Good
<ComboboxPopup>
  <ComboboxStatus>{loading ? "Loading…" : `${matches.length} results`}</ComboboxStatus>
  <ComboboxList>…</ComboboxList>
</ComboboxPopup>
```

An unexplained blank list mid-fetch announces "no such person exists", and users conclude the feature is broken before the network answers. Say what is happening where the results would appear.

## Examples [#examples]

### Multiple selection with chips [#multiple-selection-with-chips]

```tsx
"use client"

import {
  Combobox,
  ComboboxChip,
  ComboboxChips,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxValue,
} from "@/components/honest-ui/ui/combobox"

const items = [
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Orange", value: "orange" },
  { label: "Grape", value: "grape" },
  { label: "Strawberry", value: "strawberry" },
  { label: "Mango", value: "mango" },
  { label: "Pineapple", value: "pineapple" },
  { label: "Kiwi", value: "kiwi" },
  { label: "Peach", value: "peach" },
  { label: "Pear", value: "pear" },
]

export function ComboboxMultiple() {
  return (
    <div className="w-full max-w-64">
      <Combobox items={items} multiple defaultValue={[items[0], items[4]]}>
        <ComboboxChips>
          <ComboboxValue>
            {(value: { value: string; label: string }[]) => (
              <>
                {value?.map((item) => (
                  <ComboboxChip key={item.value} aria-label={item.label}>
                    {item.label}
                  </ComboboxChip>
                ))}
                <ComboboxInput
                  placeholder={value.length > 0 ? undefined : "Select an item…"}
                  aria-label="Select an item"
                />
              </>
            )}
          </ComboboxValue>
        </ComboboxChips>
        <ComboboxPopup>
          <ComboboxEmpty>No items found.</ComboboxEmpty>
          <ComboboxList>
            {(item) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxPopup>
      </Combobox>
    </div>
  )
}

```

### Grouped options [#grouped-options]

```tsx
"use client"

import * as React from "react"

import {
  Combobox,
  ComboboxCollection,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxGroupLabel,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
  ComboboxSeparator,
} from "@/components/honest-ui/ui/combobox"

// Grouped items demo
type Tag = { id: string; label: string; group: "Status" | "Priority" | "Team" }
type TagGroup = { value: string; items: Tag[] }

const tagsData: Tag[] = [
  // Status
  { id: "s-open", label: "Open", group: "Status" },
  { id: "s-in-progress", label: "In progress", group: "Status" },
  { id: "s-blocked", label: "Blocked", group: "Status" },
  { id: "s-resolved", label: "Resolved", group: "Status" },
  { id: "s-closed", label: "Closed", group: "Status" },
  // Priority
  { id: "p-low", label: "Low", group: "Priority" },
  { id: "p-medium", label: "Medium", group: "Priority" },
  { id: "p-high", label: "High", group: "Priority" },
  { id: "p-urgent", label: "Urgent", group: "Priority" },
  // Team
  { id: "t-design", label: "Design", group: "Team" },
  { id: "t-frontend", label: "Frontend", group: "Team" },
  { id: "t-backend", label: "Backend", group: "Team" },
  { id: "t-devops", label: "DevOps", group: "Team" },
  { id: "t-qa", label: "QA", group: "Team" },
  { id: "t-mobile", label: "Mobile", group: "Team" },
  { id: "t-data", label: "Data", group: "Team" },
  { id: "t-security", label: "Security", group: "Team" },
  { id: "t-platform", label: "Platform", group: "Team" },
  { id: "t-infra", label: "Infrastructure", group: "Team" },
  { id: "t-product", label: "Product", group: "Team" },
  { id: "t-marketing", label: "Marketing", group: "Team" },
  { id: "t-sales", label: "Sales", group: "Team" },
  { id: "t-support", label: "Support", group: "Team" },
  { id: "t-research", label: "Research", group: "Team" },
  { id: "t-content", label: "Content", group: "Team" },
  { id: "t-analytics", label: "Analytics", group: "Team" },
  { id: "t-operations", label: "Operations", group: "Team" },
  { id: "t-finance", label: "Finance", group: "Team" },
  { id: "t-hr", label: "HR", group: "Team" },
  { id: "t-legal", label: "Legal", group: "Team" },
  { id: "t-growth", label: "Growth", group: "Team" },
  { id: "t-partner", label: "Partner", group: "Team" },
  { id: "t-community", label: "Community", group: "Team" },
  { id: "t-docs", label: "Docs", group: "Team" },
  { id: "t-l10n", label: "Localization", group: "Team" },
  { id: "t-a11y", label: "Accessibility", group: "Team" },
  { id: "t-sre", label: "SRE", group: "Team" },
  { id: "t-release", label: "Release", group: "Team" },
  { id: "t-architecture", label: "Architecture", group: "Team" },
  { id: "t-ux", label: "UX", group: "Team" },
  { id: "t-ui", label: "UI", group: "Team" },
  { id: "t-management", label: "Management", group: "Team" },
]

function groupTags(tags: Tag[]): TagGroup[] {
  const groups: Record<string, Tag[]> = {}
  for (const t of tags) {
    ;(groups[t.group] ??= []).push(t)
  }
  const order: Array<TagGroup["value"]> = ["Status", "Priority", "Team"]
  return order.map((value) => ({ value, items: groups[value] ?? [] }))
}

const groupedTags: TagGroup[] = groupTags(tagsData)

export function ComboboxGrouped() {
  return (
    <div className="w-full max-w-64">
      <Combobox items={groupedTags}>
        <div className="flex flex-col items-start gap-2">
          <ComboboxInput
            placeholder="e.g. feature"
            aria-label="Search tags"
          />
        </div>
        <ComboboxPopup>
          <ComboboxEmpty>No tags found.</ComboboxEmpty>
          <ComboboxList>
            {(group: TagGroup) => (
              <React.Fragment key={group.value}>
                <ComboboxGroup items={group.items}>
                  <ComboboxGroupLabel>{group.value}</ComboboxGroupLabel>
                  <ComboboxCollection>
                    {(tag: Tag) => (
                      <ComboboxItem key={tag.id} value={tag}>
                        {tag.label}
                      </ComboboxItem>
                    )}
                  </ComboboxCollection>
                </ComboboxGroup>
                {group.value !== "Team" && <ComboboxSeparator />}
              </React.Fragment>
            )}
          </ComboboxList>
        </ComboboxPopup>
      </Combobox>
    </div>
  )
}

```

### Clearable [#clearable]

```tsx
"use client"

import {
  Combobox,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
} from "@/components/honest-ui/ui/combobox"

const items = [
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Orange", value: "orange" },
  { label: "Grape", value: "grape" },
  { label: "Strawberry", value: "strawberry" },
  { label: "Mango", value: "mango" },
  { label: "Pineapple", value: "pineapple" },
  { label: "Kiwi", value: "kiwi" },
  { label: "Peach", value: "peach" },
  { label: "Pear", value: "pear" },
]

export function ComboboxWithClear() {
  return (
    <div className="w-full max-w-64">
      <Combobox items={items}>
        <ComboboxInput
          placeholder="Select an item…"
          aria-label="Select an item"
          showClear
        />
        <ComboboxPopup>
          <ComboboxEmpty>No items found.</ComboboxEmpty>
          <ComboboxList>
            {(item) => (
              <ComboboxItem key={item.value} value={item}>
                {item.label}
              </ComboboxItem>
            )}
          </ComboboxList>
        </ComboboxPopup>
      </Combobox>
    </div>
  )
}

```

### In a form [#in-a-form]

```tsx
"use client";

import * as React from "react";

import { Button } from "@/components/honest-ui/ui/button";
import {
  Combobox,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
  ComboboxPopup,
} from "@/components/honest-ui/ui/combobox";
import { Field, FieldError, FieldLabel } from "@/components/honest-ui/ui/field";
import { Form } from "@/components/honest-ui/ui/form";

const items = [
  { value: "apple", label: "Apple" },
  { value: "banana", label: "Banana" },
  { value: "orange", label: "Orange" },
  { value: "grape", label: "Grape" },
  { value: "strawberry", label: "Strawberry" },
  { value: "mango", label: "Mango" },
  { value: "pineapple", label: "Pineapple" },
  { value: "kiwi", label: "Kiwi" },
  { value: "peach", label: "Peach" },
  { value: "pear", label: "Pear" },
];

export function ComboboxForm() {
  const [status, setStatus] = React.useState("");
  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const selectedItem = formData.get("item");
    const itemValue =
      items.find((item) => item.label === selectedItem)?.value || selectedItem;
    setStatus(`Submitted favorite: ${itemValue || "the selected item"}.`);
  };

  return (
    <Form onSubmit={onSubmit} className="grid w-full max-w-64 gap-4">
      <Field>
        <FieldLabel>Favorite item</FieldLabel>
        <Combobox items={items} name="item" required>
          <ComboboxInput placeholder="Select an item..." />
          <ComboboxPopup>
            <ComboboxEmpty>No results found.</ComboboxEmpty>
            <ComboboxList>
              {(item) => (
                <ComboboxItem key={item.value} value={item}>
                  {item.label}
                </ComboboxItem>
              )}
            </ComboboxList>
          </ComboboxPopup>
        </Combobox>
        <FieldError>Select an item.</FieldError>
      </Field>
      <Button type="submit">Save favorite</Button>
      <p className="text-sm text-muted-foreground" role="status">
        {status}
      </p>
    </Form>
  );
}

```

## API reference [#api-reference]

| Prop                     | Values                                | Default |
| ------------------------ | ------------------------------------- | ------- |
| `items`                  | `{ label: string; value: unknown }[]` | —       |
| `multiple`               | boolean                               | `false` |
| `value` / `defaultValue` | item or item\[]                       | —       |

Parts: `ComboboxInput`, `ComboboxTrigger`, `ComboboxPopup`, `ComboboxList`, `ComboboxItem`, `ComboboxChip(s)`, `ComboboxGroup(Label)`, `ComboboxEmpty`, `ComboboxStatus`, `ComboboxClear`, `ComboboxValue`.

See the [Base UI Combobox API](https://base-ui.com/react/components/combobox#api-reference).
