Skip to content

Combobox

Reach for Combobox when the value still comes from a fixed set of options but the set is long enough that scanning it is work: a country, a teammate, a label. It renders a text input that filters a listbox as you type, and it only ever produces values from the list — for free-form text with suggestions, that is an autocomplete, which this is not. For a short list with no input, use Select.

combobox/basic.tsx

Pass items to <Combobox.Root> — filtering, the function-child form of <Combobox.List> and <Combobox.Empty> all depend on it. Items can be plain strings, or objects: a { value, label } shape displays its label in the input automatically, any other shape needs itemToStringLabel.

Import

import { Combobox } from "@forte-ui/react";

Examples

Sizes

size on <Combobox.InputGroup> sets the field's height floor, inline padding and font size, on the same --forte-control-* scale as Input and Select.Trigger — the three line up on one row. The popup is unaffected.

combobox/sizes.tsx

Variants

variant changes only how much weight the field carries: outline reads as a form control, soft as a filled field, ghost as an inline affordance.

combobox/variants.tsx

Multiple selection

multiple on <Combobox.Root> turns the value into an array and keeps the popup open across picks — including picks made after typing a filter, so "search once, tick three results" works and the query survives to let you. Render the selection as chips: <Combobox.Chips> wraps a <Combobox.Value> function child that maps the values to <Combobox.Chip>s, with the input rendered last so it shares a line with the final chip. Backspace in the empty input removes the last chip; ArrowLeft walks into the chips, where Delete removes the highlighted one.

combobox/multiple.tsx

Input inside the popup

Render <Combobox.Input> inside <Combobox.Popup> and the combobox becomes a searchable select: a <Combobox.Trigger> holding <Combobox.Value> and <Combobox.Icon> anchors the popup, and the input waits at the top of it. The input restyles itself as a search row in this position — same part, no extra props. This is the pattern <Combobox.Label> exists for: the trigger is the form control here, and the label focuses it on click.

combobox/input-in-popup.tsx
Country

Grouped options

Pass grouped items ({ value, items } objects) to items on <Combobox.Root>, give each <Combobox.Group> its items, and render them through <Combobox.Collection> — that is what lets a query narrow each group while the headings stay put, and empty groups drop out whole.

combobox/grouped.tsx

When the options live on a server, do the filtering there: pass the results as items, set filter={null} so the combobox does not filter a second time client-side, and drive the request from onInputValueChange. <Combobox.Status> is the part that narrates the wait — it is a polite live region, so "Searching…", errors and hints reach screen readers as they change. Combobox.useFilter provides the Intl.Collator-backed matcher the fake server here uses.

combobox/async-search.tsx

The same shape works with multiple — the selected people ride along in items so their chips survive searches that no longer return them:

combobox/async-multiple.tsx

Creatable

A combobox only produces values from its list, so "create a new one" is a row in the list, not free typing: while the query matches nothing exactly, a synthetic Create "…" item rides along, and choosing it (or pressing Enter with nothing highlighted) opens a dialog instead of selecting. The real item is added and selected when the dialog's form submits.

combobox/creatable.tsx

Virtualized

Past a few thousand items the mount cost of opening dominates, and windowing the list is what keeps it fast. Set virtualized on <Combobox.Root> so highlight and ARIA bookkeeping run on indexes rather than rendered elements, read the currently filtered items with Combobox.useFilteredItems, and hand the list element to the virtualizer — the list is already the component's scroll container, so no inner scroller is needed. Up to roughly a thousand items, wrapping each item in React.memo is the simpler fix.

combobox/virtualized.tsx

Accessibility

Keyboard interactions
KeyBehaviour
Arrow DownOpens the popup from the input; moves the highlight down one item once open. The input stays focused — the highlight is virtual.
Arrow UpOpens the popup from the input; moves the highlight up one item once open.
EnterSelects the highlighted item. In a form, a combobox with a closed popup lets Enter submit as usual.
EscCloses the popup without changing the value.
A–ZTypes in the input and filters the list as you type. There is no typeahead — the input is the typeahead.
BackspaceEdits the query; in an empty multiple-selection input it removes the last chip.
Arrow Left / RightMoves the caret; from the edge of an empty multiple-selection input, moves the highlight into the chips, where Delete removes one.

The highlight loops by default: Arrow Down from the last item returns to the input, and another Arrow Down starts the list over (loopFocus on <Combobox.Root> turns this off). data-highlighted marks the highlighted item while real DOM focus stays on the input; pointer hover sets the same attribute (highlightItemOnHover, on by default), so style rows off [data-highlighted], not :hover, or keyboard users get no highlight.

Theming

Every --forte-combobox-* property below is declared on the surface that owns it, and an element's own declaration beats an inherited value — so setting one on :root, on a theme scope or on any ancestor has no effect. The popup is portalled to <body> as well, so an ancestor of the field is not even in its tree. Set each property on the part that declares it, through that part's className (an unlayered rule beats the library's @layer forte.components, whatever its specificity) or an inline style:

  • Combobox.Label declares the four label-* properties.
  • Combobox.InputGroup declares every field-* property plus --forte-combobox-input-min-width and --forte-combobox-button-size — the input and the icon buttons read those but never redeclare them, so the group is the place to set them.
  • Combobox.Trigger declares the trigger-* properties; they style its standalone select-like shape only.
  • Combobox.Chip declares the chip-* properties.
  • Combobox.Popup declares the popup-* and motion properties plus --forte-combobox-item-px, --forte-combobox-item-gap and --forte-combobox-item-indicator-size — the layout knobs the list, the items, the group labels and the status rows read but never redeclare.
  • positionerClassName reaches --forte-combobox-z-index, the only property declared on the positioner.
  • Combobox.List declares --forte-combobox-list-padding-y; Combobox.Item declares the item-* colour and font properties.

The global tokens these resolve to — --forte-color-*, --forte-control-*, --forte-radius-*, --forte-space-* — are the exception: the component only reads those and never redeclares them, so re-pointing them on :root or on a theme scope does re-skin every combobox.

Theming tokens for Combobox
PropertyControlsDefault
--forte-combobox-label-fgLabel text colourvar(--forte-color-foreground)
--forte-combobox-label-font-sizeLabel font sizevar(--forte-font-size-2)
--forte-combobox-label-font-weightLabel font weightvar(--forte-font-weight-medium)
--forte-combobox-label-gapSpace between label and controlvar(--forte-space-1)
--forte-combobox-field-heightField height floor (reset per size); the field GROWS past it when chips wrap, which is why it is a min height and not a heightvar(--forte-control-h-md)
--forte-combobox-field-pxField inline padding (reset per size)var(--forte-control-px-md)
--forte-combobox-field-font-sizeField font size (reset per size)var(--forte-font-size-2)
--forte-combobox-field-gapGap between the input, the chips and the buttonsvar(--forte-space-1)
--forte-combobox-field-radiusField corner radiusvar(--forte-radius-control)
--forte-combobox-field-border-widthField border width1px
--forte-combobox-field-widthDefault field width16rem
--forte-combobox-input-min-widthSmallest width the inline input keeps before wrapping under the chipsvar(--forte-space-8)
--forte-combobox-button-sizeSize of the icon buttons (Trigger, Clear) inside the fieldcalc( var(--forte-combobox-field-height) - var(--forte-space-1) * 2 - var(--forte-combobox-field-border-width) * 2 )
--forte-combobox-button-radiusCorner radius of the icon buttons (Trigger, Clear) inside the fieldmax( 0px, calc(var(--forte-combobox-field-radius-painted) - var(--forte-space-1)) )
--forte-combobox-chip-insetInline space a chip holds before its own label. Set it HERE, on the group — a chip's own copy cannot reach the padding that pairs with itvar(--forte-space-2)
--forte-combobox-field-bgField background (reset per variant)var(--forte-color-background)
--forte-combobox-field-bg-hoverField background on hovervar(--forte-color-panel-hover)
--forte-combobox-field-fgField text colourvar(--forte-color-foreground)
--forte-combobox-field-border-colorField border colour (reset per variant, and by data-invalid)var(--forte-color-border)
--forte-combobox-field-border-color-hoverField border colour on hovervar(--forte-color-border-strong)
--forte-combobox-trigger-heightTrigger height (reset per size; standalone shape only)var(--forte-control-h-md)
--forte-combobox-trigger-padding-xTrigger inline padding (reset per size; standalone shape only)var(--forte-control-px-md)
--forte-combobox-trigger-font-sizeTrigger font size (reset per size; standalone shape only)var(--forte-font-size-2)
--forte-combobox-trigger-gapGap between value and icon (standalone shape only)var(--forte-control-gap)
--forte-combobox-trigger-radiusTrigger corner radius (standalone shape only)var(--forte-radius-control)
--forte-combobox-trigger-border-widthTrigger border width (standalone shape only)1px
--forte-combobox-trigger-bgTrigger background (reset per variant; standalone shape only)var(--forte-color-background)
--forte-combobox-trigger-bg-hoverTrigger background on hovervar(--forte-color-panel-hover)
--forte-combobox-trigger-bg-activeTrigger background while pressed or openvar(--forte-color-panel-active)
--forte-combobox-trigger-fgTrigger text colourvar(--forte-color-foreground)
--forte-combobox-trigger-border-colorTrigger border colour (reset per variant; standalone shape only)var(--forte-color-border)
--forte-combobox-chip-bgChip backgroundvar(--forte-color-panel)
--forte-combobox-chip-fgChip text colourvar(--forte-color-foreground)
--forte-combobox-chip-bg-highlightedBackground of the chip keyboard navigation has reachedvar(--forte-color-primary-soft)
--forte-combobox-chip-fg-highlightedText colour of that chipvar(--forte-color-primary-text)
--forte-combobox-chip-radiusChip corner radiusmax( 0px, calc(var(--forte-combobox-field-radius-painted) - var(--forte-space-1)) )
--forte-combobox-chip-font-sizeChip font sizevar(--forte-font-size-1)
--forte-combobox-backdrop-z-indexStacking of the optional backdrop layer49
--forte-combobox-z-indexStacking of the positioner50
--forte-combobox-popup-bgPopup surface colourvar(--forte-color-overlay)
--forte-combobox-popup-fgPopup text colourvar(--forte-color-foreground)
--forte-combobox-popup-radiusPopup corner radiusvar(--forte-radius-surface)
--forte-combobox-popup-shadowPopup shadowvar(--forte-shadow-4)
--forte-combobox-popup-border-widthWidth of the hairline that separates the popup from the page1px
--forte-combobox-popup-border-colorColour of that hairlinevar(--forte-color-border)
--forte-combobox-travelHow far the popup slides in from, per sidevar(--forte-travel-sm)
--forte-combobox-enter-scaleScale the popup grows from and shrinks back tovar(--forte-scale-enter)
--forte-combobox-enter-durationLength of the opening gesturevar(--forte-duration-normal)
--forte-combobox-enter-easeCurve of the opening gesturevar(--forte-ease-spring-snappy)
--forte-combobox-exit-durationLength of the closing gesturevar(--forte-duration-fast)
--forte-combobox-exit-easeCurve of the closing gesturevar(--forte-ease-exit)
--forte-combobox-item-pxInline padding shared by items, group labels and status rowsvar(--forte-space-3)
--forte-combobox-item-gapGap between the indicator column and the item textvar(--forte-control-gap)
--forte-combobox-item-indicator-sizeWidth of the check-mark columnvar(--forte-space-4)
--forte-combobox-list-padding-yBlock padding inside the scrolling listvar(--forte-space-1)
--forte-combobox-item-fgItem text colourvar(--forte-color-foreground)
--forte-combobox-item-bg-highlightedBackground of the highlighted itemvar(--forte-color-primary-soft)
--forte-combobox-item-fg-highlightedText colour of the highlighted itemvar(--forte-color-primary-text)
--forte-combobox-item-fg-selectedText colour of the selected itemvar(--forte-color-primary-text)
--forte-combobox-item-font-sizeItem font sizevar(--forte-font-size-2)
--forte-combobox-group-label-fgGroup label text colourvar(--forte-color-foreground-muted)
--forte-combobox-group-label-font-sizeGroup label text sizevar(--forte-font-size-1)
--forte-combobox-group-label-trackingGroup label letter-spacing0.06em
--forte-combobox-group-label-mtSpace above a group label, dropped on the first group in the listvar(--forte-space-2)

API reference

Combobox.Root

Groups every part and owns the value, the input value and the open state. It renders no DOM element, so it takes neither className nor ref, and it forwards Base UI's own Combobox.Root props unchanged — including value, defaultValue, onValueChange, inputValue, onInputValueChange, items, multiple, filter, filteredItems, itemToStringLabel, isItemEqualToValue, autoHighlight, openOnInputClick, limit, grid, inline, virtualized, name, disabled, readOnly, required and modal. Both generics are preserved, so <Combobox.Root<Country>> types onValueChange, items and defaultValue for you.

Combobox.Label

Props for ComboboxLabel
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.InputGroup

Props for ComboboxInputGroup
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.
fullWidthbooleanfalseStretch the field to fill the width of its container.
sizeComboboxSizemdSize of the field. Height, inline padding and font size move together, and the actual numbers follow the ambient `data-forte-density` setting. Matches `Input` and `Select.Trigger`, so the three line up on one row.
variantComboboxVariantoutlineHow much visual weight the field carries. `outline` reads as a form control, `soft` as a filled field, `ghost` as an inline affordance.

Combobox.Input

Props for ComboboxInput
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Trigger

Props for ComboboxTrigger
PropTypeDefaultDescription
childrenReactNodeContents of the trigger. Defaults to a caret pair, the right children for the icon-button form inside an `<Combobox.InputGroup>` — an icon-only trigger still needs an `aria-label`. In the input-inside-popup pattern pass `<Combobox.Value>` and `<Combobox.Icon>` instead, and name the control with a `<Combobox.Label>`.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.
fullWidthbooleanfalseStretch the standalone trigger to fill the width of its container.
sizeComboboxSizemdSize of the standalone trigger. Ignored inside an `<Combobox.InputGroup>`, where the group's `size` decides.
variantComboboxVariantoutlineHow much visual weight the trigger carries. Only in play when the trigger stands alone (the input-inside-popup pattern); inside an `<Combobox.InputGroup>` the group owns the chrome and the trigger is a plain icon button.

Combobox.Value

Renders no DOM element. Takes placeholder, or a function as children to format the current value — the multiple-selection demos above use exactly that to render chips.

Combobox.Icon

Props for ComboboxIcon
PropTypeDefaultDescription
childrenReactNode<CaretUpDownIcon />Icon to render. Defaults to a caret pair whose halves spread apart while the popup is open.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Clear

Props for ComboboxClear
PropTypeDefaultDescription
childrenReactNode<XIcon />Contents of the button. Defaults to an X glyph; the button is icon-only either way, so it still needs an `aria-label`.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Chips

Props for ComboboxChips
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Chip

Props for ComboboxChip
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.ChipRemove

Props for ComboboxChipRemove
PropTypeDefaultDescription
childrenReactNode<XIcon />Contents of the button. Defaults to an X glyph; give the button an `aria-label` naming the value it removes.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Popup

Renders the floating half of the anatomy — portal, optional backdrop, positioner and the popup surface — and hands you the inside: unlike Select.Popup it does not wrap its children in a list, because what sits next to the list is the point of a combobox.

Props for ComboboxPopup
PropTypeDefaultDescription
alignAlignstartHow the popup aligns along the chosen side.
alignOffsetnumber | OffsetFunction0Extra offset in pixels along the alignment axis.
backdropbooleanfalseRender a dimming layer behind the popup. Off by default — a combobox popup is a listbox, not a dialog.
childrenReactNodeThe popup's contents — typically `<Combobox.Empty>` followed by `<Combobox.List>`, plus `<Combobox.Status>` for async lists or an `<Combobox.Input>` first for the input-inside-popup pattern.
classNamestringAdditional class name(s) for the popup surface. Applied after the internal styles so consumer utilities win without needing `!important`.
collisionPaddingPadding5Space to keep between the popup and the edge of its collision boundary.
containerHTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null> | nullRender the popup into a different container instead of `<body>`.
positionerClassNamestringAdditional class name(s) for the positioner — the absolutely positioned wrapper around the popup. Use this to reach `--forte-combobox-z-index`, the only knob declared on the positioner.
sideSidebottomWhich side of the anchor the popup opens on.
sideOffsetnumber | OffsetFunction8Gap in pixels between the anchor and the popup. The default clears the focus ring rather than sitting flush against it: unlike `Select`, focus stays on the field while the popup is open, so the group's two-tone ring (`--forte-focus-ring-offset` + `--forte-focus-ring-width`, 4px together) is painted in exactly the gap this measures.

Combobox.List

Props for ComboboxList
PropTypeDefaultDescription
childrenReactNode | ((item: any, index: number) => ReactNode)The items, groups and separators — or a function `(item, index) => ReactNode` that renders each item Base UI's filtering lets through. The function form is what keeps the list in sync with the query; it requires `items` on `<Combobox.Root>`.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Empty

Props for ComboboxEmpty
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Status

Props for ComboboxStatus
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Item

Props for ComboboxItem
PropTypeDefaultDescription
childrenReactNodeThe item's label. Rendered next to the indicator column; give the item a plain-text `aria-label` if this is not text.
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.
disabledbooleanfalseWhether the item ignores user interaction. Disabled items stay in the list and stay announced.
indicatorReactNode<CheckIcon />What marks the item as selected. Rendered inside `<Combobox.ItemIndicator>`, which only mounts while the item is selected.
valueunknownnullThe value this item selects. Objects are matched by `Object.is` unless `<Combobox.Root>` is given `isItemEqualToValue`; a `{ value, label }` shape displays its `label` in the input automatically.

Combobox.Group

Props for ComboboxGroup
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.GroupLabel

Props for ComboboxGroupLabel
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Separator

Props for ComboboxSeparator
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.
orientationOrientationhorizontalOrientation of the rule. A combobox list is vertical, so the separator between two groups runs horizontally.

Combobox.Row

Props for ComboboxRow
PropTypeDefaultDescription
classNamestringAdditional class name(s). Applied after the internal styles so consumer utilities (e.g. Tailwind) win without needing `!important`.

Combobox.Collection

Renders no DOM element. Takes a function child (item, index) => ReactNode and renders the filtered items of the nearest <Combobox.Group> — a flat list passes the function straight to <Combobox.List> instead. A direct re-export of Base UI's part.

Combobox.useFilter

Returns Intl.Collator-backed matchers — contains, startsWith, endsWith — for filtering externally. Pass the result to filter on <Combobox.Root>, or use a matcher inside your own search, as the async demos above do. Also exported standalone as useComboboxFilter.

Combobox.useFilteredItems

Returns the items the combobox is currently showing after its internal filtering — what a virtualizer should measure. Must be called from a component rendered inside <Combobox.Root>. Also exported standalone as useComboboxFilteredItems.