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 {An action in the footer#
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:
| Prop | Type | Default |
|---|---|---|
items | Item[] or { value, items }[] | none |
value / defaultValue | The selected item, or an array with multiple | none |
onValueChange | (value) => void | none |
multiple | boolean | false |
inputValue / onInputValueChange | The text in the input | none |
open / onOpenChange | boolean / (open: boolean) => void | none |
filter | A matching function, or null to turn filtering off | built in |
filteredItems | The items to show, when you filter them yourself | none |
isItemEqualToValue | (item, value) => boolean | strict equality |
disabled | boolean | false |
Parts#
| Part | What it takes |
|---|---|
ComboboxInputGroup, ComboboxInput | The box and the text field. |
ComboboxClear, ComboboxInputTrigger | Buttons to clear the value and to open the list. |
ComboboxTrigger | placeholder, and a function child that draws the value. |
ComboboxChips, ComboboxChip, ComboboxChipRemove, ComboboxValue | The chip layout for multiple. |
ComboboxContent | anchor, align, sideOffset, disableAnimation. The popup. |
ComboboxSearch | icon, actions (such as a filter button), containerClassName. |
ComboboxList, ComboboxItem | The scrolling list. An item takes value, label, description, icon and destructive. |
ComboboxGroup, ComboboxGroupLabel, ComboboxCollection | Grouped items. |
ComboboxEmpty, ComboboxStatus, ComboboxLoading | No matches, a status line for screen readers, and placeholder rows. |
ComboboxFooter, ComboboxAction | The pinned footer and a button for it, with leadingIcon. |