Combobox

A text field that filters a list as you type. Pick one value, or many as chips. Use it when a list is too long to scan, such as teammates, channels, or time zones. For a short fixed list, use Select.

Gets every new request first.

Priya Raman · #launches, #support-escalations

Usage

Combobox usageTSX
import {
  Combobox,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxInput,
  ComboboxItem,
  ComboboxList,
} from "@tulipfarm/sprout/combobox";

const fruits = ["Apple", "Apricot", "Blueberry", "Cherry", "Pear"];

<Combobox items={fruits}>
  <ComboboxInput aria-label="Choose a fruit" placeholder="Choose a fruit" showClear />
  <ComboboxContent>
    <ComboboxEmpty>No fruit found.</ComboboxEmpty>
    <ComboboxList>
      {(item) => (
        <ComboboxItem key={item} value={item}>
          {item}
        </ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxContent>
</Combobox>

Pass items to Combobox and a function to ComboboxList. The list filters as you type, and ComboboxEmpty shows when nothing matches. For objects, pass itemToStringLabel andisItemEqualToValue.

Single, multiple, and disabled

These are the three forms TulipFarm uses: a single choice with a clear button, many choices as chips with a starting value, and a disabled field.

items + showClearSearchable single choice
multiple + defaultValueMultiple choices as chips
disabledCan’t be focused or opened

Input options

ComboboxInput shows a chevron by default. Turn it off with showTrigger={false} so the list opens on typing or ↓. showClear adds a clear button that replaces the chevron once there is a value. Children are added to the group, such as a leading icon.

showTrigger (default)Chevron opens the full list
showTrigger={false}Type or press ↓ to open
showClearClear replaces the chevron once chosen
childrenExtra addon, such as a leading icon

Chips

With multiple, render ComboboxChips from ComboboxValue inside ComboboxChips, then a ComboboxChipsInput. Pass a ref from useComboboxAnchor to the chips and to ComboboxContent’s anchor so the list matches the field. Set showRemove={false} on a chip that must stay. Chips wrap when they don’t fit.

anchor={useComboboxAnchor()}List lines up with the whole field
showRemove={false}A chip that must stay
many chipsChips wrap onto new rows
ChipsTSX
const anchor = useComboboxAnchor();

<Combobox items={channels} multiple defaultValue={["launches"]}>
  <ComboboxChips ref={anchor}>
    <ComboboxValue>
      {(values: string[]) =>
        values.map((value) => <ComboboxChip key={value}>{value}</ComboboxChip>)
      }
    </ComboboxValue>
    <ComboboxChipsInput aria-label="Alert channels" placeholder="Add channel" />
  </ComboboxChips>
  <ComboboxContent anchor={anchor}>
    <ComboboxEmpty>No channel found.</ComboboxEmpty>
    <ComboboxList>
      {(channel) => <ComboboxItem key={channel} value={channel}>{channel}</ComboboxItem>}
    </ComboboxList>
  </ComboboxContent>
</Combobox>

Trigger button

ComboboxTrigger can be the whole control. Render it as a Button, show ComboboxValue inside, and put a ComboboxInput with showTrigger={false} at the top of the list. With no children it is an icon button named “Show options”.

ComboboxTrigger + ComboboxValueSearch sits inside the list
ComboboxTrigger (icon)Named “Show options” when it has no children
Assign toSam Okafor

Groups and separators

Pass groups as items. Render each one as a ComboboxGroup with its own items, a ComboboxLabel, and aComboboxCollection. Put a ComboboxSeparatorbetween groups.

ComboboxGroup + ComboboxCollectionLabels and separators between regions

States

While items load, pass an empty list, set aria-busy on the input, and say so in ComboboxEmpty. Set aria-invalid and point aria-describedby at the error. Disabled items can be reached with the arrow keys but can’t be chosen. A disabled combobox can’t be focused.

Loading tables…

No tables yet. Create one in Tables first.

Locked while the workflow runs.

API

Combobox is the Base UI combobox root. It takes items, multiple, value, defaultValue,onValueChange, itemToStringLabel, disabled, name, and every other root prop.

ComboboxInput takes showTrigger (true), showClear (false), disabled, and every input prop. Its className goes on the InputGroup.

ComboboxContent renders its own portal and positioner. It takesside (bottom), align (start), sideOffset (6), alignOffset (0), and anchor.

ComboboxChip takes showRemove (true). ComboboxTrigger, ComboboxValue, ComboboxList, ComboboxItem, ComboboxGroup, ComboboxLabel, ComboboxCollection, ComboboxEmpty, ComboboxSeparator, ComboboxChips, and ComboboxChipsInput wrap the Base UI parts and take their props. useComboboxAnchor returns a ref for the chips. Every part sets a data-slot.

Accessibility

The input is a combobox with aria-expanded and aria-autocomplete. Typing or ↓ opens the list. Arrow keys move, Enter picks, and Escape closes and keeps focus in the input. In a chips field, ← and → move between chips and Backspace removes one. Each remove button is named “Remove” plus the chip text. ComboboxEmpty is a polite live region.

The list fades and scales in over 120ms. Motion turns off when reduced motion is requested. On touch screens items are at least 44px tall. In forced-colors mode the highlighted item gets an outline.

Theme tokens

Comboboxes use --sprout-surface, --sprout-canvas, --sprout-input, --sprout-line, --sprout-hover, --sprout-secondary, --sprout-text, --sprout-muted, --sprout-focus, --sprout-danger, --sprout-shadow-overlay, and the control and panel radii. Only the focus ring follows the brand, so they work with the neutral and TulipFarm themes in light and dark mode.