

<Preview name="MultiSelectComboboxExample" />

## Overview [#overview]

The **Combobox** component provides an accessible autocomplete input that combines a text input with a filterable dropdown list. It supports both single and multi-select modes, grouped options, custom rendering, and creatable items. Built on Base UI primitives, it offers full keyboard navigation and accessibility features.

## Usage [#usage]

```tsx
import {
  Combobox,
  ComboboxChips,
  ComboboxClear,
  ComboboxContent,
  ComboboxIcon,
  ComboboxInput,
  ComboboxItem,
  ComboboxItemIndicator,
  ComboboxList,
  ComboboxValue,
  ComboboxWrapper,
} from "@tilt-legal/cubitt-components/primitives";
```

```tsx
// Basic single select
<Combobox items={options}>
  <ComboboxWrapper>
    <ComboboxInput placeholder="Select an option..." />
    <ComboboxClear />
    <ComboboxIcon />
  </ComboboxWrapper>

  <ComboboxContent>
    <ComboboxEmpty>No results found.</ComboboxEmpty>
    <ComboboxList>
      {(item) => (
        <ComboboxItem value={item}>
          <ComboboxItemIndicator />
          {item.label}
        </ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxContent>
</Combobox>
```

<Accordions type="single">
  <Accordion title="URL State Management">
    Using Cubitt's URL-state hooks, you can sync the combobox selection with the URL by providing a `paramName`. The component automatically detects common ID fields (`id`, `value`, `code`, `key`) from objects to generate clean URLs:

    ```tsx
    // Single select - synced with ?category=electronics
    <Combobox
      items={categories}
      paramName="category"
      defaultValue="all"
    >
      {/* ... */}
    </Combobox>

    // Multi-select - synced with ?tags=react,typescript,ui
    <Combobox
      items={tags}
      multiple
      paramName="tags"
      defaultValue={[]}
    >
      {/* ... */}
    </Combobox>

    // Advanced options:
    <Combobox
      items={options}
      paramName="filter"
      paramClearOnDefault={true}    // Remove param when value equals default
      paramDebounce={300}           // Debounce URL updates
      onUrlValueChange={(value) => console.log("Selected:", value)}
    >
      {/* ... */}
    </Combobox>
    ```

    For objects with non-standard ID fields, provide `itemToStringValue`:

    ```tsx
    <Combobox
      items={items}
      itemToStringValue={(item) => item.customId}
      paramName="selection"
    >
      {/* ... */}
    </Combobox>
    ```
  </Accordion>
</Accordions>

## Examples [#examples]

### Multi-Select with Chips [#multi-select-with-chips]

Display selected items as removable chips within the input.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="MultiSelectComboboxExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Combobox,
      ComboboxChip,
      ComboboxChipRemove,
      ComboboxChips,
      ComboboxContent,
      ComboboxEmpty,
      ComboboxInput,
      ComboboxItem,
      ComboboxItemIndicator,
      ComboboxList,
      ComboboxValue,
    } from "@tilt-legal/cubitt-components/primitives";

    const langs = [
      { id: "js", value: "JavaScript" },
      { id: "ts", value: "TypeScript" },
      { id: "py", value: "Python" },
    ];

    <Combobox items={langs} multiple>
      <ComboboxChips>
        <ComboboxValue>
          {(value) => (
            <>
              {value.map((language) => (
                <ComboboxChip key={language.id} aria-label={language.value}>
                  {language.value}
                  <ComboboxChipRemove />
                </ComboboxChip>
              ))}
              <ComboboxInput placeholder="e.g. TypeScript" />
            </>
          )}
        </ComboboxValue>
      </ComboboxChips>

      <ComboboxContent>
        <ComboboxEmpty>No languages found.</ComboboxEmpty>
        <ComboboxList>
          {(language) => (
            <ComboboxItem key={language.id} value={language}>
              <ComboboxItemIndicator />
              {language.value}
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>;
    ```
  </Tab>
</Tabs>

<Callout>
  Need users to create new tags on the fly? Use the [TagInput](/primitives/tag-input) component which provides a simpler API with built-in support for creating tags via Enter/comma, CSV paste parsing, and backspace removal.
</Callout>

### Multi-Select with Separate Input [#multi-select-with-separate-input]

Use a separate input field with chips displayed below.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="MultiSelectInputComboboxExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    <Combobox items={langs} multiple>
      <ComboboxWrapper>
        <ComboboxInput placeholder="e.g. TypeScript" />
        <ComboboxClear />
        <ComboboxIcon />
      </ComboboxWrapper>

      <ComboboxChips className="p-0! border-0 shadow-none">
        <ComboboxValue>
          {(value) => (
            <>
              {value.map((language) => (
                <ComboboxChip key={language.id} aria-label={language.value}>
                  {language.value}
                  <ComboboxChipRemove />
                </ComboboxChip>
              ))}
            </>
          )}
        </ComboboxValue>
      </ComboboxChips>

      <ComboboxContent>
        <ComboboxEmpty>No languages found.</ComboboxEmpty>
        <ComboboxList>
          {(language) => (
            <ComboboxItem key={language.id} value={language}>
              <ComboboxItemIndicator />
              {language.value}
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>
    ```
  </Tab>
</Tabs>

### Button Trigger with Popup Input [#button-trigger-with-popup-input]

Use a button trigger with the input inside the popup.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="PopupInputCombobox" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    <Combobox items={countries}>
      <div className="relative max-w-xs w-full">
        <ComboboxTrigger
          className="w-full"
          render={<Button variant="outline" mode="input" />}
        >
          <ComboboxValue>
            {(value) => <>{value ? value.label : "Select country"}</>}
          </ComboboxValue>
          <ComboboxIcon />
        </ComboboxTrigger>
        <ComboboxClear />
      </div>
      <ComboboxContent>
        <ComboboxInput placeholder="e.g. United Kingdom" />
        <ComboboxSeparator />
        <ComboboxEmpty>No countries found.</ComboboxEmpty>
        <ComboboxList className="overflow-y-auto max-h-[300px]">
          {(country) => (
            <ComboboxItem key={country.code} value={country}>
              <ComboboxItemIndicator />
              {country.label}
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>;
    ```
  </Tab>
</Tabs>

### Grouped Options [#grouped-options]

Organize options into labeled groups.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="ComboboxGroups" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { ComboboxCollection, ComboboxGroup, ComboboxGroupLabel } from "@tilt-legal/cubitt-components/primitives";

    const foodOptions = [
      {
        value: "Fruits",
        items: [
          { value: "apple", label: "Apple" },
          { value: "banana", label: "Banana" },
        ],
      },
      {
        value: "Vegetables",
        items: [
          { value: "carrot", label: "Carrot" },
          { value: "broccoli", label: "Broccoli" },
        ],
      },
    ];

    <Combobox items={foodOptions}>
      <ComboboxWrapper>
        <ComboboxInput placeholder="e.g. Apple" />
        <ComboboxClear />
        <ComboboxIcon />
      </ComboboxWrapper>

      <ComboboxContent className="pt-0">
        <ComboboxEmpty>No results found.</ComboboxEmpty>
        <ComboboxList>
          {(group) => (
            <ComboboxGroup key={group.value} items={group.items}>
              <ComboboxGroupLabel>{group.value}</ComboboxGroupLabel>
              <ComboboxCollection>
                {(item) => (
                  <ComboboxItem key={item.value} value={item}>
                    <ComboboxItemIndicator />
                    {item.label}
                  </ComboboxItem>
                )}
              </ComboboxCollection>
            </ComboboxGroup>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>;
    ```
  </Tab>
</Tabs>

### Size Variants [#size-variants]

The combobox input supports three size variants: `sm`, `md` (default), and `lg`.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="ComboboxSizeDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    // Small
    <ComboboxWrapper size="sm">
      <ComboboxInput size="sm" placeholder="Small size" />
    </ComboboxWrapper>

    // Medium (default)
    <ComboboxWrapper>
      <ComboboxInput placeholder="Medium size" />
    </ComboboxWrapper>

    // Large
    <ComboboxWrapper size="lg">
      <ComboboxInput size="lg" placeholder="Large size" />
    </ComboboxWrapper>
    ```
  </Tab>
</Tabs>

### Indicator Position [#indicator-position]

The indicator position can be changed to left or right.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="ComboboxIndicatorPositionExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Combobox,
      ComboboxContent,
      ComboboxInput,
      ComboboxItem,
      ComboboxItemIndicator,
      ComboboxList,
      ComboboxWrapper,
    } from "@tilt-legal/cubitt-components/primitives";

    const fruits = ["Apple", "Banana", "Orange", "Mango", "Strawberry"];

    <Combobox items={fruits}>
      <ComboboxWrapper>
        <ComboboxInput placeholder="Right indicator (default)..." />
      </ComboboxWrapper>
      <ComboboxContent>
        <ComboboxList>
          {(item) => (
            <ComboboxItem key={item} value={item}>
              <ComboboxItemIndicator />
              {item}
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>

    <Combobox items={fruits} indicatorPosition="left">
      <ComboboxWrapper>
        <ComboboxInput placeholder="Left indicator..." />
      </ComboboxWrapper>
      <ComboboxContent>
        <ComboboxList>
          {(item) => (
            <ComboboxItem key={item} value={item}>
              {item}
              <ComboboxItemIndicator />
            </ComboboxItem>
          )}
        </ComboboxList>
      </ComboboxContent>
    </Combobox>
    ```
  </Tab>
</Tabs>

### URL State (Single Select) [#url-state-single-select]

Sync a single selection with the URL. Try selecting a category and refreshing the page.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="ComboboxSingleURLStateExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Combobox,
      ComboboxClear,
      ComboboxContent,
      ComboboxEmpty,
      ComboboxIcon,
      ComboboxInput,
      ComboboxItem,
      ComboboxItemIndicator,
      ComboboxList,
      ComboboxWrapper,
      Label,
    } from "@tilt-legal/cubitt-components/primitives";

    const categories = [
      { id: "electronics", value: "Electronics" },
      { id: "books", value: "Books" },
      { id: "clothing", value: "Clothing" },
      { id: "home", value: "Home & Garden" },
      { id: "sports", value: "Sports" },
    ];

    export default function Component() {
      const id = React.useId();

      return (
        <div className="w-full max-w-xs flex flex-col gap-3">
          <Label htmlFor={id}>Category</Label>
          <Combobox
            items={categories}
            paramName="demo-category" // Automatically uses category.id for URL
            defaultValue="electronics"
          >
            <ComboboxWrapper>
              <ComboboxInput placeholder="Select category..." id={id} />
              <ComboboxClear />
              <ComboboxIcon />
            </ComboboxWrapper>

            <ComboboxContent>
              <ComboboxEmpty>No results found.</ComboboxEmpty>
              <ComboboxList>
                {(item) => (
                  <ComboboxItem key={item.id} value={item}>
                    <ComboboxItemIndicator />
                    {item.value}
                  </ComboboxItem>
                )}
              </ComboboxList>
            </ComboboxContent>
          </Combobox>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### URL State (Multi-Select) [#url-state-multi-select]

Sync multiple selections with the URL using comma-separated values.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="ComboboxMultiURLStateExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Combobox,
      ComboboxChip,
      ComboboxChipRemove,
      ComboboxChips,
      ComboboxContent,
      ComboboxEmpty,
      ComboboxInput,
      ComboboxItem,
      ComboboxItemIndicator,
      ComboboxList,
      ComboboxValue,
      Label,
    } from "@tilt-legal/cubitt-components/primitives";

    const tags = [
      { id: "react", value: "React" },
      { id: "typescript", value: "TypeScript" },
      { id: "tailwind", value: "Tailwind CSS" },
      { id: "nextjs", value: "Next.js" },
      { id: "nodejs", value: "Node.js" },
      { id: "ui", value: "UI/UX" },
    ];

    export default function Component() {
      const containerRef = React.useRef<HTMLDivElement | null>(null);
      const id = React.useId();

      return (
        <Combobox items={tags} multiple paramName="demo-tags" defaultValue={[]}>
          <div className="w-full max-w-xs flex flex-col gap-3">
            <Label htmlFor={id}>Tags</Label>
            <ComboboxChips ref={containerRef}>
              <ComboboxValue>
                {(value) => (
                  <React.Fragment>
                    {value.map((tag) => (
                      <ComboboxChip key={tag.id} aria-label={tag.value}>
                        {tag.value}
                        <ComboboxChipRemove />
                      </ComboboxChip>
                    ))}
                    <ComboboxInput
                      placeholder={value.length > 0 ? "" : "Select tags..."}
                      id={id}
                    />
                  </React.Fragment>
                )}
              </ComboboxValue>
            </ComboboxChips>
          </div>

          <ComboboxContent anchor={containerRef}>
            <ComboboxEmpty>No results found.</ComboboxEmpty>
            <ComboboxList>
              {(tag) => (
                <ComboboxItem key={tag.id} value={tag}>
                  <ComboboxItemIndicator />
                  {tag.value}
                </ComboboxItem>
              )}
            </ComboboxList>
          </ComboboxContent>
        </Combobox>
      );
    }
    ```
  </Tab>
</Tabs>

## Props [#props]

### Combobox [#combobox]

| Prop                 | Type                               | Default   | Description                                   |
| -------------------- | ---------------------------------- | --------- | --------------------------------------------- |
| `items`              | `T[]`                              | —         | The list of items to display in the combobox. |
| `multiple`           | `boolean`                          | `false`   | Enable multi-select mode.                     |
| `value`              | `T \| T[]`                         | —         | The controlled value(s).                      |
| `defaultValue`       | `T \| T[]`                         | —         | The default value(s) when uncontrolled.       |
| `onValueChange`      | `(value: T \| T[]) => void`        | —         | Callback when the selection changes.          |
| `inputValue`         | `string`                           | —         | The controlled input text value.              |
| `onInputValueChange` | `(value: string) => void`          | —         | Callback when the input text changes.         |
| `onOpenChange`       | `(open: boolean, details) => void` | —         | Callback when the popup open state changes.   |
| `disabled`           | `boolean`                          | `false`   | Disable the combobox.                         |
| `indicatorPosition`  | `"left" \| "right"`                | `"right"` | Position of the selection indicator in items. |
| `className`          | `string`                           | —         | Additional CSS classes for the combobox.      |

#### URL State Props [#url-state-props]

When provided with a `paramName`, the combobox will sync its selection with URL parameters via TanStack Router search params.

| Prop                  | Type                                | Default | Description                                                                                                      |
| --------------------- | ----------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| `paramName`           | `string`                            | —       | URL parameter name for syncing state with URL. Enables URL state management.                                     |
| `onUrlValueChange`    | `(value: T \| T[] \| null) => void` | —       | Callback when URL parameter value changes.                                                                       |
| `paramClearOnDefault` | `boolean`                           | `true`  | Remove URL param when value equals default.                                                                      |
| `paramThrottle`       | `number`                            | —       | Throttle URL updates in milliseconds.                                                                            |
| `paramDebounce`       | `number`                            | —       | Debounce URL updates in milliseconds.                                                                            |
| `itemToStringValue`   | `(item: T) => string`               | —       | Function to extract string value from item for URL serialization. Auto-detects common ID fields if not provided. |

### ComboboxInput [#comboboxinput]

| Prop          | Type                   | Description                     |
| ------------- | ---------------------- | ------------------------------- |
| `size`        | `"sm" \| "md" \| "lg"` | The size variant of the input.  |
| `placeholder` | `string`               | Placeholder text for the input. |
| `className`   | `string`               | Additional CSS classes.         |

### ComboboxItem [#comboboxitem]

| Prop        | Type     | Description                     |
| ----------- | -------- | ------------------------------- |
| `value`     | `T`      | The value this item represents. |
| `className` | `string` | Additional CSS classes.         |

### ComboboxChips [#comboboxchips]

| Prop        | Type                   | Description                          |
| ----------- | ---------------------- | ------------------------------------ |
| `size`      | `"sm" \| "md" \| "lg"` | The size variant matching the input. |
| `className` | `string`               | Additional CSS classes.              |

### ComboboxWrapper [#comboboxwrapper]

A wrapper component that provides input styling and serves as the positioning anchor for the popup. Uses the same styles as `InputWrapper` for consistency.

| Prop        | Type                   | Description                          |
| ----------- | ---------------------- | ------------------------------------ |
| `size`      | `"sm" \| "md" \| "lg"` | The size variant matching the input. |
| `className` | `string`               | Additional CSS classes.              |

### ComboboxContent [#comboboxcontent]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxList [#comboboxlist]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxEmpty [#comboboxempty]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxGroup [#comboboxgroup]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxGroupLabel [#comboboxgrouplabel]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxItemIndicator [#comboboxitemindicator]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxChip [#comboboxchip]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxChipRemove [#comboboxchipremove]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxValue [#comboboxvalue]

| Prop        | Type                                            | Description                                      |
| ----------- | ----------------------------------------------- | ------------------------------------------------ |
| `children`  | `ReactNode \| ((value: T \| T[]) => ReactNode)` | Content or render function for displaying value. |
| `className` | `string`                                        | Additional CSS classes.                          |

### ComboboxIcon [#comboboxicon]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxClear [#comboboxclear]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |

### ComboboxTrigger [#comboboxtrigger]

| Prop        | Type     | Description             |
| ----------- | -------- | ----------------------- |
| `className` | `string` | Additional CSS classes. |
