Combobox

A select you can search. Pick one or several options from a list that is long enough to need filtering.

import {
  Combobox,
  ComboboxClear,

When to use it

Use a combobox when people choose from a list and the list is too long to scan: countries, customers, branches, products. They type to narrow it down, and the value they end up with is always one of the options.

If the list is short, a Select is simpler. If people may type something that is not in the list at all, such as a free-text search, use Autocomplete, where the text stays the value.

Import

import {
  Combobox,
  ComboboxInputGroup,
  ComboboxInput,
  ComboboxClear,
  ComboboxInputTrigger,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxList,
  ComboboxItem,
} from "@hel/ui/combobox"

Two ways to lay it out

The input in the page. ComboboxInputGroup holds a text input, so people type right where the field is. This is the layout of the example at the top, and it suits forms.

The search inside the popup. ComboboxTrigger looks like a select. Clicking it opens a popup that starts with a ComboboxSearch box. This suits filters and pickers where the closed state should show a short summary, not an input.

Both share the same popup: a ComboboxList that scrolls, an optional ComboboxEmpty for no matches, and an optional ComboboxFooter that stays pinned below the list.

The list is a render function, like Autocomplete's. Give each ComboboxItem a value and a label. When the values are objects, also give the combobox isItemEqualToValue, so it can tell which item is selected.

Examples

Picking several, with a summary

With multiple, the value is an array. The trigger takes a function that turns it into the text on the closed button. Here it says the name for one customer and a count for more. Items can have a description as a second line.

import * as React from "react"
import { User } from "lucide-react"

Chips

For tags and skills, show the choices as chips inside the input. Put ComboboxChips around the value, render a ComboboxChip with a ComboboxChipRemove for each one, and put the ComboboxInput after them. Give the popup the chips box as its anchor, so it lines up with the whole field and not just the cursor.

import * as React from "react"

import {

Grouped options

Pass items as { value, items } groups. Render each group with ComboboxGroup and ComboboxGroupLabel, and its rows with ComboboxCollection. Groups with no matches disappear as people type.

import {
  Combobox,
  ComboboxClear,

Search on a server

When the options come from an API, turn off the built-in filter with filter={null}, pass your results as filteredItems, and fetch in onInputValueChange. Keep the chosen item in items so it still shows after a new search no longer returns it. While the first results load, ComboboxLoading shows placeholder rows, and ComboboxStatus tells screen readers what is going on.

import * as React from "react"

import {

ComboboxFooter stays in place while the list scrolls, which makes it the spot for something like "Create category". The action here uses what was typed, and is disabled when that name already exists. For a field whose whole job is creating options, see Creatable Select.

import * as React from "react"
import { CirclePlus } from "lucide-react"

Loading placeholder

ComboboxLoading draws skeleton rows shaped like the options. rows sets how many, description and icon add a second line and a leading square to each row, animation is "pulse" or "shimmer", and label is what screen readers read. Pass children to replace the rows with your own placeholder.

Labels

The clear button, the open button and the popup's search box have English labels. Add your own under messages in the Hel config, with the key combobox.

Props

Combobox

Combobox takes the props of the Base UI combobox root. The ones used most are:

PropTypeDefault
itemsItem[] or { value, items }[]none
value / defaultValueThe selected item, or an array with multiplenone
onValueChange(value) => voidnone
multiplebooleanfalse
inputValue / onInputValueChangeThe text in the inputnone
open / onOpenChangeboolean / (open: boolean) => voidnone
filterA matching function, or null to turn filtering offbuilt in
filteredItemsThe items to show, when you filter them yourselfnone
isItemEqualToValue(item, value) => booleanstrict equality
disabledbooleanfalse

Parts

PartWhat it takes
ComboboxInputGroup, ComboboxInputThe box and the text field.
ComboboxClear, ComboboxInputTriggerButtons to clear the value and to open the list.
ComboboxTriggerplaceholder, and a function child that draws the value.
ComboboxChips, ComboboxChip, ComboboxChipRemove, ComboboxValueThe chip layout for multiple.
ComboboxContentanchor, align, sideOffset, disableAnimation. The popup.
ComboboxSearchicon, actions (such as a filter button), containerClassName.
ComboboxList, ComboboxItemThe scrolling list. An item takes value, label, description, icon and destructive.
ComboboxGroup, ComboboxGroupLabel, ComboboxCollectionGrouped items.
ComboboxEmpty, ComboboxStatus, ComboboxLoadingNo matches, a status line for screen readers, and placeholder rows.
ComboboxFooter, ComboboxActionThe pinned footer and a button for it, with leadingIcon.