# Combobox

## Summary

Combobox lets a user search and select one or more values from a list. Use it
when the list benefits from filtering, when the selected values need to appear
in the field, or when the popup needs supporting actions such as creating a new
option.

Use Combobox when the selected value is saved, submitted, assigned, or otherwise
becomes part of the user's work.

## Anatomy

| Part        | Description                                                       |
| ----------- | ----------------------------------------------------------------- |
| Field       | Label, optional note, description, and error treatment            |
| Trigger     | Visible field that opens the popup and displays the current value |
| Search      | Input used to filter options, either in the trigger or popup      |
| Content     | Popup or mobile sheet that contains options and affordances       |
| Item        | Selectable option with optional prefix, description, and suffix   |
| Empty state | Message shown when search has no matching options                 |
| Actions     | Sticky action rows below the option list                          |
| Footer      | Sticky region for multiple-selection affordances                  |

## Behavior

#### Opening and closing

Combobox opens when the trigger is clicked, tapped, or activated by keyboard. It
closes when the user selects a single-select option, clicks or taps outside the
popup, or presses Esc.

Multiple-selection comboboxes stay open while values are selected so users can
choose more than one option.

#### Search

Search filters the option list as the user types. Search can happen directly in
the trigger or inside the popup, depending on the trigger pattern.

#### Empty state

When no options match, the empty state appears above actions and footer content.
This keeps creation actions and multiple-selection controls in predictable
locations.

#### Actions and footer

Actions are anchored below the option list. The multiple-selection footer is
anchored below options and actions when present.

#### Small screens

On small screens, Combobox automatically presents the popup as a bottom-anchored
sheet. Consumers use the same Combobox API across desktop and mobile web.

## Trigger patterns

Use `Combobox.TriggerInput` when the user searches directly in the field.

Use `Combobox.TriggerValue` when the closed field should behave more like a
Select and search belongs inside the popup.

Use `Combobox.TriggerMultipleValue` for multi-select fields that show selected
chips in the closed field and keep search inside the popup.

## Variants

#### Single select

Use single select when the user can choose one value.

#### Multiple select

Use multiple select when the user can choose several values and needs selected
values to remain visible in the field.

#### Popup search

Use popup search when the trigger should display the current value and search
should happen after the popup opens.

## Item content

Items can be plain text for the default row treatment, or composed with
`Combobox.ItemPrefix`, `Combobox.ItemLabel`, `Combobox.ItemDescription`, and
`Combobox.ItemSuffix` for richer option content. The selected checkmark is
included by default.

## Empty and loading states

Combobox includes a default empty state so search results always resolve in the
right place, above actions and footer content. Customize the message through
`Combobox.Content`, render your own `Combobox.Empty`, or opt out when the
experience needs to own that region.

Loading uses four glimmer rows by default. The row count and loading content can
be customized when a product needs a different loading treatment.

## Actions

Actions are for work outside option selection, such as creating or inviting an
item. The default action row uses the leading add icon shown in Figma, while
`Combobox.ActionPrefix` and `Combobox.ActionLabel` are available when the row
needs custom content.

## Multiple-selection footer

Use `Combobox.SelectionFooter` for the opinionated multiple-select footer. It
keeps selected count, clear, and select-all controls in a consistent order.

Select-all behavior is product-owned: the app decides whether "all" means
visible, filtered, loaded, paginated, or server-known results.

## Content Guidelines

#### Labels

Use a short noun label that names the value being selected.

| ✅ Do         | ❌ Don't                          |
| ------------ | -------------------------------- |
| Team member  | Choose which team member to use  |
| Job type     | Select one of these job types    |
| Service area | Search and pick a service region |

#### Search placeholder

Use placeholder text that describes what can be searched. Keep it short.

| ✅ Do                | ❌ Don't                                   |
| ------------------- | ----------------------------------------- |
| Search team members | Start typing to search all team members   |
| Search clients      | Enter the name of the client to find them |
| Search job types    | Filter the dropdown                       |

#### Item text

Use the item label for the selectable value. Use descriptions only when they
help distinguish similar options.

| ✅ Do                         | ❌ Don't                                  |
| ---------------------------- | ---------------------------------------- |
| Ryan Clearwater              | Select Ryan Clearwater                   |
| Ryan Clearwater + Sales lead | Ryan Clearwater, who is the sales lead   |
| Downtown + Service area      | Downtown service area available for jobs |

#### Actions

Use actions for work outside option selection, such as creating or inviting an
item. Action labels should be verb-first and sentence-cased.

| ✅ Do               | ❌ Don't                  |
| ------------------ | ------------------------ |
| Create team member | Team member creation     |
| Invite team member | Send invitation to staff |
| Add client         | Client                   |

## Do's and Don'ts

#### Do

* ✅ Use Combobox when the list benefits from search or filtering
* ✅ Use enough item context to distinguish similar values
* ✅ Keep empty state above actions and footer controls
* ✅ Use the multiple-selection footer for clear and select-all affordances
* ✅ Define what "select all" means before including it

#### Don't

* ❌ Don't use Combobox for a tiny static list where Select is simpler
* ❌ Don't hide empty state behind actions or footer controls
* ❌ Don't assume "select all" means every server result
* ❌ Don't mix unrelated actions into the popup
* ❌ Don't overload item rows with content that slows scanning

## Rich item data

Options can include more than a primary label when extra context helps users
choose confidently. Use descriptions, prefixes, or suffixes when similar options
need to be distinguished at a glance. Those fields can also be made filterable,
so search can match the details users naturally type.

## Accessibility

Combobox should have a clear accessible label. Keyboard behavior and active item
management are provided by Base UI.

#### Keyboard navigation

| Key                | Behavior                                               |
| ------------------ | ------------------------------------------------------ |
| Tab                | Moves focus to the trigger, actions, or footer buttons |
| Up and Down arrows | Moves through available options                        |
| Enter              | Selects the highlighted option or activates an action  |
| Esc                | Closes the popup                                       |

## Related components

* To choose one value from a small static list, use [Select](../Select/Select.md)
* To present actions that do not filter or select form values, use
  [Menu](../Menu/Menu.md)


## Composition

Combobox is a composable field built on `ComboboxPrimitive`. The sugared
component provides common recipes while preserving access to lower-level pieces
when a product flow needs more control.

### Trigger recipes

Most implementations should start with one of the trigger recipes.

#### `Combobox.TriggerInput`

Use `Combobox.TriggerInput` when the user should search directly in the field.

```tsx
<Combobox label="Team member" items={teamMembers}>
  <Combobox.TriggerInput placeholder="Search team members" />
  <Combobox.Content>
    <Combobox.List>
      {member => (
        <Combobox.Item key={member.id} value={member}>
          {member.name}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>
```

#### `Combobox.TriggerValue`

Use `Combobox.TriggerValue` when the closed field should display the selected
value and the search input should live inside the popup.

```tsx
<Combobox label="Team member" items={teamMembers}>
  <Combobox.TriggerValue placeholder="Select team member" />
  <Combobox.Content>
    <Combobox.Input placeholder="Search team members" />
    <Combobox.List>
      {member => (
        <Combobox.Item key={member.id} value={member}>
          {member.name}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>
```

#### `Combobox.TriggerMultipleValue`

Use `Combobox.TriggerMultipleValue` when the closed field should display
selected chips and the search input should live inside the popup.

```tsx
<Combobox multiple label="Assigned team" items={teamMembers}>
  <Combobox.TriggerMultipleValue getItemLabel={member => member.name} />
  <Combobox.Content>
    <Combobox.Input placeholder="Search team members" />
    <Combobox.List>
      {member => (
        <Combobox.Item key={member.id} value={member}>
          {member.name}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>
```

#### `Combobox.TriggerMultipleWithInput`

Use `Combobox.TriggerMultipleWithInput` when multi-select chips and search
should both live in the trigger.

```tsx
<Combobox multiple label="Assigned team" items={teamMembers}>
  <Combobox.TriggerMultipleWithInput getItemLabel={member => member.name} />
  <Combobox.Content>
    <Combobox.List>
      {member => (
        <Combobox.Item key={member.id} value={member}>
          {member.name}
        </Combobox.Item>
      )}
    </Combobox.List>
  </Combobox.Content>
</Combobox>
```

### Field text

Pass `label`, `description`, and `error` on `Combobox` for the default Atlantis
field treatment. If the layout needs different placement, compose
`Combobox.Label`, `Combobox.Description`, and `Combobox.Error` manually.

```tsx
<Combobox
  label="Team member"
  description="Search by name"
  error="Choose a team member"
  items={teamMembers}
>
  {/* trigger and content */}
</Combobox>
```

### Empty state

`Combobox.Content` renders `"No results found"` by default when search has no
matches. The empty state is placed after options and before actions or footer
content.

Use the `empty` prop to customize the default message:

```tsx
<Combobox.Content empty="No team members found">
  {/* options */}
</Combobox.Content>
```

When the empty state needs custom markup, omit the automatic empty state and
render `Combobox.Empty` directly:

```tsx
<Combobox.Content empty={null}>
  <Combobox.List>{/* options */}</Combobox.List>
  <Combobox.Empty>No team members match this search.</Combobox.Empty>
</Combobox.Content>
```

### Actions

Use `Combobox.Action` for interactive rows that are not selectable options, such
as creating a new customer or inviting a team member. Use `Combobox.Item` when
the row represents a value the Combobox can select.

```tsx
<Combobox.Content empty={`No matches for "${query}"`}>
  <Combobox.List>{/* options */}</Combobox.List>
  <Combobox.Actions>
    <Combobox.Action onClick={() => createTeamMember(query)}>
      Create team member
    </Combobox.Action>
  </Combobox.Actions>
</Combobox.Content>
```

Clicking an action runs the action's handler and leaves the popup open. If an
action should close the popup, control `open` from product state and set it to
`false` in the action handler. If the action opens another overlay, such as a
Dialog, also coordinate that from product state so focus and closing behavior
match the product flow.

```tsx
const [open, setOpen] = useState(false);

<Combobox open={open} onOpenChange={setOpen}>
  <Combobox.TriggerInput />
  <Combobox.Content>
    <Combobox.List>{/* options */}</Combobox.List>
    <Combobox.Actions>
      <Combobox.Action
        onClick={() => {
          createTeamMember();
          setOpen(false);
        }}
      >
        Create team member
      </Combobox.Action>
    </Combobox.Actions>
  </Combobox.Content>
</Combobox>;
```

### Footer and select all

`Combobox.SelectionFooter` is the default multiple-select footer. It owns the
visual ordering for selected count, clear, and select all. Use
`showSelectedCount={false}` when the count should be omitted.

Select-all behavior is intentionally not automatic. Products must decide the
candidate set and pass `onSelectAll`.

```tsx
const selectAllState = Combobox.getSelectAllState({
  selectedValues,
  candidateValues: visibleValues,
});
```

The candidate set can be visible results, filtered results, loaded results, the
current page, or another product-defined set.

```tsx
const [selectedMembers, setSelectedMembers] = useState([]);
const selectAllState = Combobox.getSelectAllState({
  selectedValues: selectedMembers,
  candidateValues: visibleMembers,
});

<Combobox
  multiple
  value={selectedMembers}
  onValueChange={setSelectedMembers}
  items={visibleMembers}
>
  <Combobox.TriggerMultipleValue getItemLabel={member => member.name} />
  <Combobox.Content>
    <Combobox.Input placeholder="Search team members" />
    <Combobox.List>{/* options */}</Combobox.List>
    <Combobox.SelectionFooter
      selectAllState={selectAllState}
      onSelectAll={() =>
        setSelectedMembers(
          Combobox.getNextSelectAllValues({
            selectedValues: selectedMembers,
            candidateValues: visibleMembers,
          })
        )
      }
    />
  </Combobox.Content>
</Combobox>;
```

## Controlled and uncontrolled usage

Combobox follows Base UI's value model. Use `value` and `onValueChange` when the
app owns selection state, or `defaultValue` for uncontrolled selection.

Single-select values are one item or `null`. Multi-select values are arrays.
When values are objects, use `itemToString` for display and `itemToValue` for
form submission.

```tsx
<Combobox
  value={selectedMember}
  onValueChange={setSelectedMember}
  itemToString={member => member?.name ?? ""}
  itemToValue={member => member.id}
  items={teamMembers}
>
  {/* trigger and content */}
</Combobox>
```

## Filtering and data strategy

For local data, pass `items` and optionally customize the root `filter` prop.
`Combobox.useFilter` exposes Base UI's filter helper with Atlantis locale wired
in, so matching handles locale-sensitive text consistently.

```tsx
const filter = Combobox.useFilter();

<Combobox
  items={teamMembers}
  filter={(member, query) =>
    filter.contains(member.name, query) ||
    filter.contains(member.department, query) ||
    filter.contains(member.role, query)
  }
>
  {/* trigger and content */}
</Combobox>;
```

`Combobox.useFilteredItems` exposes Base UI's filtered item collection for
custom list rendering, virtualization, or pagination affordances. It does not
load additional data on its own; it only reads the filtered items currently
known to Combobox.

Combobox does not own pagination, but it gives products the pieces to build it.
Pass the currently loaded items into `items`, load additional pages from product
state or an API call, and compose loading UI inside the popup with
`Combobox.Status` and `ActivityIndicator` when needed. For local large lists,
use `Combobox.useFilteredItems` when custom rendering or virtualization needs
access to the current filtered collection.

```tsx
<Combobox.Content>
  <Combobox.Input placeholder="Search team members" />
  <Combobox.List>{/* loaded options */}</Combobox.List>
  {isLoadingMore && (
    <Combobox.Status>
      <ActivityIndicator aria-label="Loading more team members" />
    </Combobox.Status>
  )}
</Combobox.Content>
```

For async, paginated, virtualized, or server-filtered data, keep the data
strategy in product code. Fetch from input changes, pass the current loaded or
windowed items to Combobox, and disable client filtering when the server already
returned filtered results.

> **NOTICE:** Combobox does not infer total result sets, page boundaries, or select-all
> semantics for server-backed data.

## Mobile web

Combobox automatically switches to a bottom-anchored sheet at the Atlantis small
breakpoint. This uses Base UI's Combobox modal and backdrop behavior, not
Atlantis `BottomSheet`, so consumers do not need a separate mobile API.


## Props

### Web

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `actionsRef` | `RefObject<Actions>` | No | — | A ref to imperative actions. - `unmount`: Manually unmounts the combobox. Call this after any externally controlled c... |
| `autoComplete` | `string` | No | — | Provides a hint to the browser for autofill. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attribu... |
| `autoHighlight` | `boolean` | No | `false` | Whether the first matching item is highlighted automatically while filtering. |
| `className` | `string` | No | — |  |
| `defaultInputValue` | `string | number | readonly string[]` | No | — | The uncontrolled input value when initially rendered.  To render a controlled input, use the `inputValue` prop instead. |
| `defaultOpen` | `boolean` | No | `false` | Whether the popup is initially open.  To render a controlled popup, use the `open` prop instead. |
| `defaultValue` | `ComboboxValueType<Value, Multiple>` | No | — | The uncontrolled selected value of the combobox when it's initially rendered.  To render a controlled combobox, use t... |
| `description` | `string` | No | — |  |
| `disabled` | `boolean` | No | `false` | Whether the component should ignore user interaction. |
| `error` | `string` | No | — |  |
| `filter` | `(itemValue: Value, query: string, itemToString?: (itemValue: Value) => string) => boolean` | No | — | Filter function used to match items vs input query. |
| `filteredItems` | `readonly any[] | readonly Group<any>[]` | No | — | Filtered items to display in the list. When provided, the list will use these items instead of filtering the `items` ... |
| `form` | `string` | No | — | Identifies the form that owns the internal input. Useful when the combobox is rendered outside the form. |
| `grid` | `boolean` | No | `false` | Whether list items are presented in a grid layout. When enabled, arrow keys navigate across rows and columns inferred... |
| `highlightItemOnHover` | `boolean` | No | `true` | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differenti... |
| `id` | `string` | No | — | The id of the component. |
| `inline` | `boolean` | No | `false` | Whether the list is rendered inline without using the component's own popup.  Specify `open` unconditionally in conju... |
| `inputRef` | `Ref<HTMLInputElement>` | No | — | A ref to the hidden input element. |
| `inputValue` | `string | number | readonly string[]` | No | — | The input value of the combobox. Use when controlled. |
| `invalid` | `boolean` | No | — |  |
| `isItemEqualToValue` | `(itemValue: Value, value: Value) => boolean` | No | — | Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when it... |
| `items` | `readonly any[] | readonly Group<any>[]` | No | — | The items to be displayed in the list. Can be either a flat array of items or an array of groups with items. |
| `itemToStringLabel` | `(itemValue: Value) => string` | No | — | When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a str... |
| `itemToStringValue` | `(itemValue: Value) => string` | No | — | When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a str... |
| `label` | `ReactNode` | No | — |  |
| `limit` | `number` | No | `-1` | The maximum number of items to display in the list. |
| `locale` | `LocalesArgument` | No | — | The locale to use for string comparison. Defaults to the user's runtime locale. |
| `loopFocus` | `boolean` | No | `true` | Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The ... |
| `modal` | `boolean` | No | `false` | Determines if the popup enters a modal state when open. - `true`: user interaction is limited to the popup: document ... |
| `multiple` | `boolean` | No | `false` | Whether multiple items can be selected. |
| `name` | `string` | No | — | Identifies the field when a form is submitted. |
| `onInputValueChange` | `(inputValue: string, eventDetails: ChangeEventDetails) => void` | No | — | Event handler called when the input value changes. |
| `onItemHighlighted` | `(highlightedValue: Value, eventDetails: HighlightEventDetails) => void` | No | — | Callback fired when an item is highlighted or unhighlighted. Receives the highlighted item value (or `undefined` if n... |
| `onOpenChange` | `(open: boolean, eventDetails: ChangeEventDetails) => void` | No | — | Event handler called when the popup is opened or closed. |
| `onOpenChangeComplete` | `(open: boolean) => void` | No | — | Event handler called after any animations complete when the popup is opened or closed. |
| `onValueChange` | `(value: ComboboxValueType<Value, Multiple> | (Multiple extends true ? never : null), eventDetails: ChangeEventDetails) => void` | No | — | Event handler called when the selected value of the combobox changes. |
| `open` | `boolean` | No | — | Whether the popup is currently open. Use when controlled. |
| `openOnInputClick` | `boolean` | No | `true` | Whether the popup opens when clicking the input. |
| `readOnly` | `boolean` | No | `false` | Whether the user should be unable to choose a different option from the popup. |
| `required` | `boolean` | No | `false` | Whether the user must choose a value before submitting a form. |
| `size` | `ComboboxSize` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `value` | `ComboboxValueType<Value, Multiple>` | No | — | The selected value of the combobox. Use when controlled. |
| `virtualized` | `boolean` | No | `false` | Whether the items are being externally virtualized. |
