---
title: Combobox
description: A searchable select input with autocomplete functionality.
icon: search
author:
  name: eblairmckee
  url: https://github.com/eblairmckee
sidebar:
  badge: Updated
search:
  keywords: [autocomplete, typeahead, searchable select]
related:
  - /select
  - /multi-select
  - /command
mode: wide
---

<PoweredBy
  packages={[
    { name: 'Base UI', url: 'https://base-ui.com/react/components/combobox' },
    { name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/combobox' },
    { name: 'Lucide', url: 'https://lucide.dev/' },
  ]}
/>

### Installation

```package-install
npx shadcn@latest add @redpanda/combobox
```

<ComponentPreview name="combobox-demo" />

## When to use

Use this decision tree to determine when to use the Combobox component:

```mermaid
graph TD;
    A[❓ Need user to select from options?] --> B{What type of selection?};
    B --> C[✅ Search + select from list];
    B --> D[⚠️ Simple dropdown selection];
    B --> E[⚠️ Multiple selections needed];

    C --> F[❓ How many options?];
    D --> G[⚠️ Use Select instead];
    E --> H[⚠️ Use MultiSelect instead];

    F --> I["✅ Large list (50+ items)"];
    F --> J["ℹ️ Small list (<20 items)"];

    I --> K[❓ Need grouping?];
    J --> L[❓ Need search functionality?];

    K --> M[✅ Set group on each option];
    K --> N[ℹ️ Use a flat option list];

    L --> O[✅ Use Combobox for search];
    L --> P[⚠️ Consider Select instead];

    classDef recommended fill:#4ade80,stroke:#22c55e,stroke-width:2px,color:#000;
    classDef discouraged fill:#fbbf24,stroke:#f59e0b,stroke-width:2px,color:#000;
    classDef decision fill:#60a5fa,stroke:#3b82f6,stroke-width:2px,color:#fff;
    classDef neutral fill:#9ca3af,stroke:#6b7280,stroke-width:2px,color:#fff;

    class C,I,M,O recommended;
    class D,E,G,H,P discouraged;
    class A,B,F,K,L decision;
    class J,N neutral;
```

## Usage

```tsx
import { Combobox } from '@/components/ui/combobox';
```

```tsx expandable
'use client';

import React from 'react';
import { Combobox } from '@/components/ui/combobox';

const frameworks = [
  {
    value: 'next.js',
    label: 'Next.js',
  },
  {
    value: 'sveltekit',
    label: 'SvelteKit',
  },
  {
    value: 'nuxt.js',
    label: 'Nuxt.js',
  },
  {
    value: 'remix',
    label: 'Remix',
  },
  {
    value: 'astro',
    label: 'Astro',
  },
];

export function ComboboxDemo() {
  const [value, setValue] = React.useState('');

  return <Combobox options={frameworks} value={value} onChange={setValue} placeholder="Select framework..." />;
}
```

## Behavior

Combobox is built on [Base UI Combobox](https://base-ui.com/react/components/combobox). The input is an ARIA
combobox: focus stays in the input while `aria-activedescendant` points at the highlighted option, so screen readers
announce each option as you move through the list.

| Key                     | Action                                                                                                                                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ArrowDown` / `ArrowUp` | Open the list, then move the highlight. The highlight wraps around the list.                                                                                                          |
| `Enter`                 | Pick the highlighted option. With `creatable`, commit the typed text when nothing is highlighted, even with the list closed. Never submits a surrounding form while the list is open. |
| `Escape`                | Close the list. With the list closed, clear the value.                                                                                                                                |
| `ArrowRight`            | With the caret at the end of the text, pick the highlighted option.                                                                                                                   |
| `Home` / `End`          | Move the caret to the start or end of the text.                                                                                                                                       |

- Typing filters options by label and value, and highlights the first match.
- Opening the list highlights the selected option. `Enter` keeps it; clicking it again clears the value.
- Deleting all text clears the value.
- Closing the list without a pick restores the selected label. With `creatable`, text typed for a new option stays in
  the input while it has focus, so `Enter` can still commit it; leaving the field restores the selected label.
- Disabled options can be highlighted and are announced as disabled, but can't be picked.
- While the list is open, the rest of the page is hidden from assistive technology.

## Props

### Combobox

| Prop | Type | Default | Description |
| - | - | - | - |
| `options` | `ComboboxOption[]` | - | Array of options to display in the dropdown. |
| `value?` | `string` | - | The currently selected value. |
| `onChange` | `(value: string) => void` | - | Callback when the selected value changes. |
| `placeholder?` | `string` | - | Placeholder text when no value is selected. |
| `disabled?` | `boolean` | - | Whether the combobox is disabled. |
| `clearable?` | `boolean` | `true` | Shows a clear (X) button while a value is selected. |
| `creatable?` | `boolean` | - | If true, offers a "Create" option at the end of the list when the typed text matches no existing option. |
| `onCreateOption?` | `(value: string) => void` | - | Callback when a new option is created via the create action. Receives the trimmed text, which is also passed to onChange. |
| `onInputValueChange?` | `(value: string) => void` | - | Called when the user edits the input text. Drive async search from here; it does not fire when picking an option fills in its label. |
| `isLoading?` | `boolean` | - | Shows an inline loading status and suppresses the empty state while async options load. |
| `emptyState?` | `React.ReactNode` | - | Replaces the "No options found." message. Ignored while isLoading. |
| `renderOption?` | `(option: ComboboxOption) => React.ReactNode` | - | Renders the content of each option row. The selected check icon is still rendered by the component. |
| `createLabel?` | `string` | `"option"` | Noun used in the create prompt (e.g. "tag", "context"). Displays as "Type to create a new {createLabel}..." when empty. |
| `start?` | `React.ReactNode \| null` | `<Search />` | Content for the start slot of the input. Defaults to a search icon. Pass null to hide. |
| `className?` | `string` | - | Additional CSS classes for the input container. |
| `onOpen?` | `() => void` | - | Callback when the dropdown opens. |
| `onClose?` | `() => void` | - | Callback when the dropdown closes. |
| `defaultOpen?` | `boolean` | - | Renders the list open on mount, focusing the input unless preventAutoFocusOnOpen is set. |
| `preventAutoFocusOnOpen?` | `boolean` | - | Keeps a defaultOpen combobox from focusing its input on mount. |
| `container?` | `HTMLElement` | - | Element to portal the list into. Defaults to the nearest portal container, then the document body. |
| `testId?` | `string` | - | Set as data-testid on the dropdown popup. |
| `inputTestId?` | `string` | - | Set as data-testid on the input. |
| `id?` | `string` | - | Set on the inner input, so a sibling <label htmlFor> both associates with it and names it. |
| `aria-label?` | `string` | - | Names the control when no visible label points at it. Set on the inner input. |
| `aria-labelledby?` | `string` | - | Ids of the elements that name the control, for a label the component does not render. |

### ComboboxOption

| Prop | Type | Default | Description |
| - | - | - | - |
| `value` | `string` | - | Value passed to onChange when the option is picked. |
| `label` | `string` | - | Text shown in the list and in the input; used for filtering with value. |
| `group?` | `string` | - | Section heading. Options sharing a group are listed together, in first-seen order. |
| `disabled?` | `boolean` | - | Renders the option but blocks picking it. |
| `data?` | `unknown` | - | Arbitrary payload handed back to renderOption. |
| `testId?` | `string` | - | Set as data-testid on the option. |
| `groupTestId?` | `string` | - | Set as data-testid on the option group; the first option of a group sets it. |

### Labeling

The trigger is an `input` with `role="combobox"`, so it needs an accessible name of its own — a
`placeholder` is not one. Give it an `id` and point a `Label` at it, or pass `aria-label` /
`aria-labelledby` when the name lives in an element the component does not render.

```tsx lineNumbers
<Label htmlFor="framework">Framework</Label>
<Combobox id="framework" onChange={setValue} options={frameworks} value={value} />
```

## Examples

### Basic Combobox

<ComponentPreview name="combobox-demo" description="A combobox with a list of frameworks." />

### Form Integration

<ComponentPreview name="combobox-form-demo" description="A combobox component used in a form with validation." />

### Creatable Options

<ComponentPreview
  name="combobox-creatable-demo"
  description="A combobox that offers a create option at the end of the list for adding new options on the fly."
/>

### Disabled State

<ComponentPreview name="combobox-disabled-demo" description="A disabled combobox component." />

### Grouped Combobox

<ComponentPreview name="combobox-grouped-demo" description="A combobox with grouped options." />

### Timezone Picker

<ComponentPreview
  name="combobox-timezone-demo"
  description="Timezone picker combobox with searchable IANA zones and current-offset display."
/>

### Async Remote Search

<ComponentPreview
  name="combobox-async-demo"
  description="Async combobox with debounced remote search, a loading indicator, and a custom empty state."
/>

Pair `onInputValueChange` with a debounced fetch and feed the resulting `options` back in. Toggle `isLoading` while the
request is in flight to show an inline spinner and suppress the default "No options found." message. Results are still
filtered by the typed text, the first match is highlighted as soon as results arrive, and the selected option keeps its
label even when a later search no longer returns it.

```tsx lineNumbers
<Combobox
  isLoading={loading}
  onChange={setValue}
  onInputValueChange={setQuery}
  options={options}
  emptyState="No topics match that query."
  value={value}
/>
```

### Rich Option Rows

<ComponentPreview
  name="combobox-with-description-demo"
  description="Combobox options rendered with primary label and secondary descriptive metadata."
/>

Use `renderOption` to render each row as a two-line label + metadata cell. Attach arbitrary per-option data via the
optional `data` field.

```tsx lineNumbers
<Combobox
  options={BROKERS}
  renderOption={(option) => {
    const meta = option.data as BrokerMeta | undefined;
    return (
      <div className="flex flex-col">
        <span className="font-medium text-sm">{option.label}</span>
        {meta ? (
          <span className="text-subtle text-xs">
            {meta.host} · {meta.dc}
          </span>
        ) : null}
      </div>
    );
  }}
  onChange={setValue}
  value={value}
/>
```

### Disabled Items

<ComponentPreview
  name="combobox-disabled-items-demo"
  description="Combobox with individual options disabled for permission-gated choices."
/>

Mark a `ComboboxOption` as `disabled: true` to render it as a non-selectable row. Compose with `renderOption` to
surface the reason it's unavailable.

```tsx lineNumbers
const OPTIONS: ComboboxOption[] = [
  { value: 'reader', label: 'Reader' },
  { value: 'maintainer', label: 'Maintainer', disabled: true },
  { value: 'admin', label: 'Admin', disabled: true },
];
```

## Credits

- Built on [Base UI Combobox](https://base-ui.com/react/components/combobox).
- We take our inspiration from [Shadcn UI](https://ui.shadcn.com/docs/components/combobox) for the combobox component and style.
