---
layout: ~/layouts/MdxDocLayout.astro
title: "Combobox"
description: "A searchable select component that allows users to filter and select from a list of options."
sourceFile: "components/combobox"
baseUIComponent: "combobox"
---

import ComponentExample from "~/components/docs/ComponentExample.astro";
import ComponentSection from "~/components/docs/ComponentSection.astro";
import CodeBlock from "~/components/docs/CodeBlock.astro";
import PropsTable from "~/components/docs/PropsTable.astro";
import {
  ComboboxDemo,
  ComboboxSearchableInsideDemo,
  ComboboxSearchableSelectDemo,
  ComboboxGroupedDemo,
  ComboboxMultipleDemo,
  ComboboxWithFieldDemo,
  ComboboxDisabledDemo,
  ComboboxDisabledItemsDemo,
  ComboboxErrorDemo,
  ComboboxSizesDemo,
  ComboboxSizesSearchableInsideDemo,
  ComboboxCustomTriggerDemo,
} from "~/components/demos/ComboboxDemo";

{/* Hero Demo */}

<ComponentSection>
  <ComponentExample demo="ComboboxDemo">
    <ComboboxDemo client:load />
  </ComponentExample>
</ComponentSection>

{/* Installation */}

<ComponentSection>

## Installation

### Barrel

<CodeBlock code={`import { Combobox } from "@cloudflare/kumo";`} lang="tsx" />

### Granular

  <CodeBlock
    code={`import { Combobox } from "@cloudflare/kumo/components/combobox";`}
    lang="tsx"
  />
</ComponentSection>

{/* Usage */}

<ComponentSection>

## Usage

```tsx
import { useState } from "react";
import { Combobox } from "@cloudflare/kumo";

const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];

export default function Example() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Combobox value={value} onValueChange={setValue} items={fruits}>
      <Combobox.TriggerInput placeholder="Select a fruit" />
      <Combobox.Content>
        <Combobox.Empty />
        <Combobox.List>
          {(item) => (
            <Combobox.Item key={item} value={item}>
              {item}
            </Combobox.Item>
          )}
        </Combobox.List>
      </Combobox.Content>
    </Combobox>
  );
}
```

</ComponentSection>

{/* Examples */}

<ComponentSection>

## Examples

### Sizes

<p>
  The Combobox supports four size variants that match the Input component: `xs`,
  `sm`, `base` (default), and `lg`.
</p>
<ComponentExample demo="ComboboxSizesDemo">
  <ComboboxSizesDemo client:load />
</ComponentExample>

  <p>
    Size also applies to `TriggerValue` (searchable inside variant):
  </p>
  <ComponentExample demo="ComboboxSizesSearchableInsideDemo">
    <ComboboxSizesSearchableInsideDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### Searchable Item (Inside)

  <p>
    A searchable select component inside popup that allows users to filter and
    select.
  </p>
  <ComponentExample demo="ComboboxSearchableInsideDemo">
    <ComboboxSearchableInsideDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### Searchable Select with Placeholder

  <p>
    Use `TriggerValue` with a `placeholder` prop to create a searchable Select-style field.
    The placeholder is displayed until a value is selected.
  </p>
  <ComponentExample demo="ComboboxSearchableSelectDemo">
    <ComboboxSearchableSelectDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### Custom Trigger

<p>
  Use <code>Combobox.Trigger</code> with a <code>render</code> prop to replace
  the default input-like trigger with your own element. Pair with
  <code>Combobox.Value</code> to display the selected value. Useful for account
  switchers, sidebar navigation, or anywhere the default chrome doesn't fit.
</p>
<ComponentExample demo="ComboboxCustomTriggerDemo">
  <ComboboxCustomTriggerDemo client:load />
</ComponentExample>

</ComponentSection>

<ComponentSection>

### Grouped

  <p>Group items into categories using the Group and GroupLabel components.</p>
  <ComponentExample demo="ComboboxGroupedDemo">
    <ComboboxGroupedDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### Multiple

  <p>Allow users to select multiple options from the list.</p>
  <ComponentExample demo="ComboboxMultipleDemo">
    <ComboboxMultipleDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### With Field

  <p>Add label and description using the built-in Field wrapper.</p>
  <ComponentExample demo="ComboboxWithFieldDemo">
    <ComboboxWithFieldDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### Disabled

  <p>Pass the `disabled` prop to prevent interaction. Works with both `TriggerInput` and `TriggerValue`.</p>
  <ComponentExample demo="ComboboxDisabledDemo">
    <ComboboxDisabledDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### Disabled Items

  <p>Pass the `disabled` prop to an individual `Combobox.Item` to make it non-selectable. Disabled rows are rendered with a muted style and skipped during keyboard navigation selection.</p>
  <ComponentExample demo="ComboboxDisabledItemsDemo">
    <ComboboxDisabledItemsDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

### Error State

  <p>Display validation errors with the error prop.</p>
  <ComponentExample demo="ComboboxErrorDemo">
    <ComboboxErrorDemo client:load />
  </ComponentExample>
</ComponentSection>

<ComponentSection>

## Filtering

<p>
  Filtering is case- and accent-insensitive by default, powered by
  `Intl.Collator` under the hood. For string items, no custom `filter` is
  needed.
</p>
<p>
  When filtering on a property of object items, use `Combobox.useFilter()` to
  preserve the built-in accent-insensitive matching:
</p>

```tsx
function LanguagePicker() {
  const { contains } = Combobox.useFilter();

  const filter = useCallback(
    (item: Language, query: string) => contains(item.label, query),
    [contains],
  );

  return (
    <Combobox items={languages} filter={filter}>
      {/* ... */}
    </Combobox>
  );
}
```

<p>
  To disable filtering entirely (e.g. when results come from a server), pass
  `filter={null}`:
</p>

```tsx
<Combobox items={results} filter={null}>
  ...
</Combobox>
```

</ComponentSection>

<ComponentSection>

## Customizing Dropdown Height

<p>
  By default, `Combobox.Content` has a max height of `24rem` (384px) or the
  available viewport space, whichever is smaller. The dropdown scrolls
  automatically when content exceeds this height.
</p>
<p>To customize the max height, pass a className to `Combobox.Content`:</p>

```tsx
// Shorter dropdown (200px)
<Combobox.Content className="max-h-[200px]">

// Taller dropdown (500px)
<Combobox.Content className="max-h-[500px]">

// Use Tailwind presets
<Combobox.Content className="max-h-64">  // 256px
<Combobox.Content className="max-h-96">  // 384px (same as default)
```

</ComponentSection>

<ComponentSection>

## API Reference

### Combobox

<p>Root component for the searchable select.</p>
<PropsTable component="Combobox" />

### Combobox.Content

<p>Dropdown container for the list.</p>
<PropsTable component="Combobox.Content" />

### Combobox.Item

<p>Individual selectable option.</p>
<PropsTable component="Combobox.Item" />

### Additional Sub-components

  <ul class="list-disc list-inside space-y-1">
    <li>`Combobox.TriggerInput` - Single-select input trigger</li>
    <li>`Combobox.TriggerValue` - Button trigger showing selected value</li>
    <li>`Combobox.TriggerMultipleWithInput` - Multi-select with chips</li>
    <li>`Combobox.Input` - Search input inside dropdown</li>
    <li>`Combobox.List` - List container with render prop</li>
    <li>`Combobox.Group` - Group container for categorized items</li>
    <li>`Combobox.GroupLabel` - Header label for a group</li>
    <li>`Combobox.Collection` - Items container within a group</li>
    <li>`Combobox.Chip` - Selected item chip</li>
    <li>`Combobox.Empty` - Empty state message</li>
  </ul>
</ComponentSection>
