DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Combobox

Filterable select bound to an option list — single or multi-select with chips, groups, async loading, creatable rows, and the full field anatomy.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import {
  ComboboxRoot,
  ComboboxInputGroup,
  ComboboxInput,
  ComboboxTrigger,
  ComboboxClear,
  ComboboxPopup,
  ComboboxList,
  ComboboxItem,
  ComboboxEmpty,
} from "@tessinaui/ui";
<ComboboxRoot items={fruits} label="Fruit" value={value} onValueChange={setValue}>
  <ComboboxInputGroup>
    <ComboboxInput placeholder="Search…" />
    <ComboboxClear />
    <ComboboxTrigger />
  </ComboboxInputGroup>
  <ComboboxPopup>
    <ComboboxList>
      {fruits.map((f) => (
        <ComboboxItem key={f} value={f}>{f}</ComboboxItem>
      ))}
    </ComboboxList>
    <ComboboxEmpty />
  </ComboboxPopup>
</ComboboxRoot>

Pass the option collection through items — that is what the built-in filter narrows as the user types. Static children without items render fine but do not filter themselves.

When to use — and when not to

  • Combobox — pick from a list the user would search rather than scan: long lists, remote data, tag/multi entry with chips. The filter input is the core of the control.
  • A short, known, fixed list with no filtering is a Select. If the trigger should show the chosen value as a button (not an editable input), that is also Select — its multiple already does "2 selected" summary triggers.
  • A ⌘K overlay of commands and navigation is Command — same wiring, different pattern (centered dialog, shortcut hints, action rows).
  • A page-level search box with history, scopes and a submit action is Search. The combobox-recents example shows where the boundary runs.
  • Free-form token entry where arbitrary text becomes chips (emails, keywords) with paste/comma splitting is Tokenizer; Combobox chips always come from the option list (plus an explicit create row).
  • A compact unit/currency selector attached to an input is FieldDropdown.
  • Rows that act (delete, invite, navigate) belong in DropdownMenu.

Examples

Default

A single-select combobox with a label, clear button, and filterable list.

Multiple

Multi-select mode renders chosen values as removable chips inside the input, and option rows switch to checkbox visuals.

Grouped

Options organized under group labels, with separators between sections.

Sizes

Five height scales — xs to xl — on the field family's shared type ladder (12/14/14/16/16, 16px floor on phones).

Validation

errorMessage replaces the supporting text, implies the error intent, sets aria-invalid, and is announced politely. required stars the label; optional suffixes it.

Ghost variant

Borderless fields for toolbars and property rows — with visuallyHiddenLabel keeping the accessible name.

On-color

Inside a colored Surface the tone resolves automatically: well, chips, rings and meta text all read the surface contract.

States

Disabled, read-only (focusable, value locked), and disabled options inside an enabled list.

Trailing meta

trailingText puts muted meta on the row end — dial codes, counts, prices — outside the accessible name. Leading flags via leadingIcon.

Match highlight

ComboboxHighlight bolds the matched substring while the accessible name stays whole.

Checkbox items

Multi-select filter panels: select-all / clear-all in the pinned footer with a live "n/N" count.

Max selection

Cap the selection in onValueChange — removals always pass, the helper counts what's left, and full lists disable the rest.

Creatable

Unknown input gets a "Create ‹query›" row that promotes the text to a real, selected option.

Async loading

Debounced remote search: min-characters hint, ComboboxStatus while loading, an error state with retry, and a result count in the footer.

Recents

openOnInputClick surfaces recent and trending groups before the user types.

Empty state with action

ComboboxEmpty accepts arbitrary children — pair the "no results" message with a way forward.

Footer action

A pinned row under the list for manage/config links — it never scrolls away.

Panel mode (search inside the popup)

A button-shaped trigger with the search input inside the popup — the branch-picker pattern.

Scrollable

Long lists cap their height and scroll inside the popup.

Loading

Skeletons that mirror the real anatomy — including the chip row in multiple mode.

API Reference

ComboboxRoot props

All Base UI Combobox.Root props are forwarded (items, value, onValueChange, inputValue, onInputValueChange, filter, filteredItems, multiple, limit, locale, autoHighlight, openOnInputClick, virtualized, grid, inline, actionsRef, …). House props:

PropTypeDefaultDescription
size"xs" | "sm" | "md" | "lg" | "xl""md"Field height; text reads the family ladder
intent"none" | "error" | "warning" | "success" | "info""none"Border, ring and supporting color
rounded"none" | "sm" | "md" | "lg" | "xl" | "full""full"Radius cascade — popup and items follow
variant"outline" | "ghost""outline"Bordered well vs borderless toolbar field
tone"default" | "on-color"from SurfaceOn colored plates; resolves from Surface context
labelPlacement"top" | "inside" | "start""top"Logical label layout
labelWidth"sm" | "md" | "lg" | "xl" | "auto" | CSS length"md"Width of the start-label column, so sibling fields in a form share one control column — sm 6rem · md 8rem · lg 11rem · xl 14rem. Labels wrap, never truncate. Only applies with labelPlacement="start", from sm up
labelstring—Label text, associated via htmlFor
requiredboolean—Star on the label + required on the input
optionalboolean—"(optional)" label suffix; ignored with required
infoTextstring—Info hint at the label-row end, in the describedby chain
supportingTextstring—Helper below the field (polite region)
errorMessagestring—Replaces the helper, implies error intent, sets aria-invalid
visuallyHiddenLabelboolean—Keep the name for AT, hide the label visually
popupPadding"narrow" | "default" | "wide""default"Popup inner padding
dir"ltr" | "rtl"—Logical direction (wraps in a DirectionProvider when set)
labelPosition——Deprecated — use labelPlacement
width——Deprecated — use popupPadding
wrapperClassName——Deprecated — use className

Without a label, give the input its own aria-label — the input must have a name.

ComboboxItem props

PropTypeDescription
valueanyThe value this item represents
disabledbooleanPrevents selection
leadingIconReactNodeIcon before the label
leadingAvatarReactNodeAvatar before the label
descriptionstringSecond line — outside the name, wired as the description
trailingTextReactNodeMuted end-of-row meta, outside the name

In multiple mode every item renders a checkbox visual; single-select items render the trailing checkmark.

ComboboxHighlight props

PropTypeDescription
textstringThe full option text
querystringSubstring to bold (case-insensitive, every occurrence)

ComboboxLeadingIcon props

PropTypeDescription
onClick() => voidRenders a real focusable <button> — name it with aria-label. Omit for a decorative slot

ComboboxPopup props

PropTypeDescription
positionerPropsPositioner propsSide, align, offsets, and style — the popup defaults to the anchor's width via --anchor-width; override it here

ComboboxList

The listbox and the scrollport are the same element — Base UI puts role="listbox" and the id aria-controls resolves against on this node — so the list keeps a native scrollport wearing the house scrollbarSkin rather than composing the ScrollArea component. ScrollArea cannot re-measure a list whose content height changes from filtering; see When you can't use it: scrollbarSkin. It caps at min(18rem, 100dvh - 6rem) and scrolls.

ComboboxFooter

A pinned row under ComboboxList — manage links, bulk actions, result counts. It bleeds to the popup edge and never scrolls with the list.

ComboboxFilterTabs / ComboboxFilterTab

PropTypeDescription
value / onValueChangestring / (v) => voidControlled active filter
countnumberCount badge on a tab

Plain aria-pressed toggles in a labelled group — see the accessibility note.

ComboboxSkeleton props

PropTypeDefaultDescription
size / roundedas Rootmd / fullGeometry from the same maps
showLabelbooleanfalseLabel row
labelPlacement"top" | "inside" | "start""top"Mirrors the real layout
labelWidth"sm" | "md" | "lg" | "xl" | "auto" | CSS length"md"Width of the start-label column, so sibling fields in a form share one control column — sm 6rem · md 8rem · lg 11rem · xl 14rem. Labels wrap, never truncate. Only applies with labelPlacement="start", from sm up
showSupportingTextbooleanfalseHelper row
showLeadingIconbooleanfalseLeading icon circle
showChipsbooleanfalseChip pills for multiple mode

Accessibility

The input itself carries role="combobox" with aria-expanded, aria-controls and aria-activedescendant; DOM focus never leaves the input while arrowing through options. The visible (or sr-only) label names the input via htmlFor; supportingText / errorMessage / infoText join aria-describedby, the meta row is a polite live region, and errorMessage sets aria-invalid. Option rows announce their label alone — description becomes the option's description, trailingText stays out of both.

Keyboard

KeyAction
Tab / Shift+TabInto / out of the input (the chevron button is not a tab stop)
↓ / ↑Open the popup; move the highlight (wraps)
EnterAccept the highlighted option and close
EscClose the popup
Home / EndMove the text caret, not the list highlight
Printable keysType and filter — Space types a space, it never selects
← / → on an empty multi inputWalk the chips
Backspace on an empty multi inputRemove the last chip

AT matrix

ATStatus
VoiceOver (macOS)Verified — name, role, expanded state, option announcements, polite error swap
NVDAUntested — cannot run on this hardware
JAWSUntested — cannot run on this hardware

Known limitations

  • Filter tabs are a pointer/AT refinement. The popup keeps DOM focus on the input, so the tab buttons are not keyboard-reachable while the popup is open. They are deliberately aria-pressed toggles, not a tablist. Keep a keyboard route to the same filtering — typing, or controls outside the popup.
  • Interactive rows inside the footer (ComboboxFooter) are reachable by pointer and AT cursor, not by Tab while the popup is open.

Migrating

BeforeAfter
labelPosition="outside-top"labelPlacement="top"
labelPosition="inside"labelPlacement="inside" — now renders a real floating label
labelPosition="outside-left"labelPlacement="start"
width="narrow | wide"popupPadding="narrow | wide"
wrapperClassNameclassName

Deprecated props keep working for one minor. Visual changes in this release: the focus ring keeps the border underneath (field model), input/item text adopts the shared field ladder (sm 12→14, lg 14→16 on desktop), rounded gains xl, and the pill popup cap is now the §2 panel radius.

Color PickerCommand

On this page

PlaygroundInstallationUsageWhen to use — and when not toExamplesDefaultMultipleGroupedSizesValidationGhost variantOn-colorStatesTrailing metaMatch highlightCheckbox itemsMax selectionCreatableAsync loadingRecentsEmpty state with actionFooter actionPanel mode (search inside the popup)ScrollableLoadingAPI ReferenceComboboxRoot propsComboboxItem propsComboboxHighlight propsComboboxLeadingIcon propsComboboxPopup propsComboboxListComboboxFooterComboboxFilterTabs / ComboboxFilterTabComboboxSkeleton propsAccessibilityKeyboardAT matrixKnown limitationsMigrating