Combobox
An input combined with a filterable list of predefined values.
API Reference
Original primitive API
Behavior, accessibility details, and low-level props are documented by Base UI.
Choosing the right component
Use Combobox when selection should come from predefined options, but users still need an input to
search or narrow that list.
- Choose
Comboboxfor searchable selection from a known dataset. - Choose
Autocompletewhen free-form input is valid and suggestions are optional guidance. - Choose
Selectwhen input typing is unnecessary and a button-triggered list is enough.
Basic
import { Combobox, ComboboxField, ComboboxFieldLabel, ComboboxInputGroup, ComboboxInput, ComboboxControlActions, ComboboxClear, ComboboxTrigger, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxItem, ComboboxItemIndicator, ComboboxItemText,} from "moduix";import { useId } from "react";export function ComboboxDemo() { const id = useId(); return ( <Combobox items={fruits} itemToStringLabel={(item) => item.label}> <ComboboxField> <ComboboxFieldLabel htmlFor={id}>Choose fruit</ComboboxFieldLabel> <ComboboxInputGroup> <ComboboxInput id={id} placeholder="e.g. Mango" /> <ComboboxControlActions> <ComboboxClear aria-label="Clear selection" /> <ComboboxTrigger aria-label="Open options" /> </ComboboxControlActions> </ComboboxInputGroup> </ComboboxField> <ComboboxContent> <ComboboxEmpty>No fruits found.</ComboboxEmpty> <ComboboxList> {(item) => ( <ComboboxItem key={item.id} value={item}> <ComboboxItemIndicator /> <ComboboxItemText>{item.label}</ComboboxItemText> </ComboboxItem> )} </ComboboxList> </ComboboxContent> </Combobox> );}const fruits = [ { id: "apple", label: "Apple", value: "apple" }, { id: "banana", label: "Banana", value: "banana" }, { id: "mango", label: "Mango", value: "mango" },];Full list of component variables available for project-level overrides.
| Property | Default | Description |
|---|---|---|
| --combobox-action-bg | transparent | Default: transparent. |
| --combobox-action-bg-hover | var(--color-muted) | Default: var(--color-muted). |
| --combobox-action-color-hover | var(--color-foreground) | Default: var(--color-foreground). |
| --combobox-action-radius | var(--radius-sm) | Default: var(--radius-sm). |
| --combobox-action-size | 1.5rem | Default: 1.5rem. |
| --combobox-actions-gap | 0.125rem | Default: 0.125rem. |
| --combobox-actions-offset-right | 0.5rem | Default: 0.5rem. |
| --combobox-arrow-height | 0.625rem | Default: 0.625rem. |
| --combobox-arrow-inline-offset | 13px | Default: 13px. |
| --combobox-arrow-size | 8px | Default: 8px. |
| --combobox-arrow-stroke-color | var(--combobox-popup-border-color) | Default: var(--combobox-popup-border-color). |
| --combobox-arrow-width | 1.25rem | Default: 1.25rem. |
| --combobox-backdrop-bg | var(--backdrop-bg, var(--color-overlay)) | Default: var(--backdrop-bg, var(--color-overlay)). |
| --combobox-backdrop-blur | 2px | Default: 2px. |
| --combobox-backdrop-transition | var(--transition-default) | Default: var(--transition-default). |
| --combobox-bg | var(--color-background) | Default: var(--color-background). |
| --combobox-border-color | var(--color-border) | Default: var(--color-border). |
| --combobox-border-width | var(--border-width-sm) | Default: var(--border-width-sm). |
| --combobox-check-padding-x-start | 0.625rem | Default: 0.625rem. |
| --combobox-chip-bg | var(--color-muted) | Default: var(--color-muted). |
| --combobox-chip-color | var(--color-foreground) | Default: var(--color-foreground). |
| --combobox-chip-font-size | var(--text-sm) | Default: var(--text-sm). |
| --combobox-chip-gap | var(--spacing-1) | Default: var(--spacing-1). |
| --combobox-chip-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). |
| --combobox-chip-min-height | 1.625rem | Default: 1.625rem. |
| --combobox-chip-padding-left | 0.5rem | Default: 0.5rem. |
| --combobox-chip-padding-right | 0.375rem | Default: 0.375rem. |
| --combobox-chip-padding-y | 0.1875rem | Default: 0.1875rem. |
| --combobox-chip-radius | var(--radius-sm) | Default: var(--radius-sm). |
| --combobox-chip-remove-bg-hover | var(--color-overlay-foreground) | Default: var(--color-overlay-foreground). |
| --combobox-chip-remove-icon-size | 0.75rem | Default: 0.75rem. |
| --combobox-chip-remove-radius | var(--radius-sm) | Default: var(--radius-sm). |
| --combobox-chip-remove-size | 1rem | Default: 1rem. |
| --combobox-chips-gap | var(--spacing-1) | Default: var(--spacing-1). |
| --combobox-chips-input-height | 1.75rem | Default: 1.75rem. |
| --combobox-chips-input-min-width | 4rem | Default: 4rem. |
| --combobox-chips-input-padding-x | 0.5rem | Default: 0.5rem. |
| --combobox-chips-padding | var(--spacing-1) | Default: var(--spacing-1). |
| --combobox-color | var(--color-foreground) | Default: var(--color-foreground). |
| --combobox-control-height | var(--size-lg) | Default: var(--size-lg). |
| --combobox-empty-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). |
| --combobox-empty-font-size | var(--text-sm) | Default: var(--text-sm). |
| --combobox-empty-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). |
| --combobox-empty-padding-x | 1rem | Default: 1rem. |
| --combobox-empty-padding-y | 0.75rem | Default: 0.75rem. |
| --combobox-field-gap | 0.375rem | Default: 0.375rem. |
| --combobox-focus-ring-color | var(--color-ring) | Default: var(--color-ring). |
| --combobox-group-label-bg | var(--color-popover) | Default: var(--color-popover). |
| --combobox-group-label-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). |
| --combobox-group-label-font-size | var(--text-xs) | Default: var(--text-xs). |
| --combobox-group-label-font-weight | var(--weight-semibold) | Default: var(--weight-semibold). |
| --combobox-group-label-line-height | var(--line-height-text-xs) | Default: var(--line-height-text-xs). |
| --combobox-group-label-padding-bottom | 0.35rem | Default: 0.35rem. |
| --combobox-group-label-padding-top | 0.35rem | Default: 0.35rem. |
| --combobox-group-label-padding-x | 0.625rem | Default: 0.625rem. |
| --combobox-group-padding-bottom | var(--spacing-1) | Default: var(--spacing-1). |
| --combobox-highlight-bg | var(--color-foreground) | Default: var(--color-foreground). |
| --combobox-highlight-color | var(--color-background) | Default: var(--color-background). |
| --combobox-highlight-inset-x | var(--spacing-1) | Default: var(--spacing-1). |
| --combobox-highlight-radius | var(--radius-sm) | Default: var(--radius-sm). |
| --combobox-icon-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). |
| --combobox-icon-size | 0.875rem | Default: 0.875rem. |
| --combobox-icon-svg-size | 1rem | Default: 1rem. |
| --combobox-inline-input-container-padding-bottom | var(--spacing-2) | Default: var(--spacing-2). |
| --combobox-inline-input-container-padding-top | var(--spacing-2) | Default: var(--spacing-2). |
| --combobox-inline-input-container-padding-x | var(--spacing-2) | Default: var(--spacing-2). |
| --combobox-inline-input-divider-color | var(--combobox-border-color) | Default: var(--combobox-border-color). |
| --combobox-inline-input-divider-style | solid | Default: solid. |
| --combobox-inline-input-divider-width | var(--border-width-sm) | Default: var(--border-width-sm). |
| --combobox-input-group-padding-x | 0 | Default: 0. |
| --combobox-input-padding-x-end | 3.25rem | Default: 3.25rem. |
| --combobox-input-padding-x-start | 0.875rem | Default: 0.875rem. |
| --combobox-input-placeholder-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). |
| --combobox-item-bg | transparent | Default: transparent. |
| --combobox-item-border-color | transparent | Default: transparent. |
| --combobox-item-border-radius | 0 | Default: 0. |
| --combobox-item-border-width | 0 | Default: 0. |
| --combobox-item-color | var(--color-foreground) | Default: var(--color-foreground). |
| --combobox-item-font-size | var(--text-sm) | Default: var(--text-sm). |
| --combobox-item-gap | var(--spacing-2) | Default: var(--spacing-2). |
| --combobox-item-indicator-bg | transparent | Default: transparent. |
| --combobox-item-indicator-border-color | transparent | Default: transparent. |
| --combobox-item-indicator-border-width | 0 | Default: 0. |
| --combobox-item-indicator-icon-size | 0.75rem | Default: 0.75rem. |
| --combobox-item-indicator-padding | 0 | Default: 0. |
| --combobox-item-indicator-radius | 0 | Default: 0. |
| --combobox-item-indicator-size | 0.75rem | Default: 0.75rem. |
| --combobox-item-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). |
| --combobox-item-min-height | 2rem | Default: 2rem. |
| --combobox-item-padding-x-end | 1rem | Default: 1rem. |
| --combobox-item-padding-y | var(--spacing-2) | Default: var(--spacing-2). |
| --combobox-item-text-content-gap | var(--spacing-2) | Default: var(--spacing-2). |
| --combobox-item-text-icon-color | currentColor | Default: currentColor. |
| --combobox-item-text-icon-size | 1rem | Default: 1rem. |
| --combobox-label-font-size | var(--text-sm) | Default: var(--text-sm). |
| --combobox-label-font-weight | var(--weight-medium) | Default: var(--weight-medium). |
| --combobox-label-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). |
| --combobox-list-max-height | var(--combobox-popup-max-height) | Default: var(--combobox-popup-max-height). |
| --combobox-list-padding-y | 0.25rem | Default: 0.25rem. |
| --combobox-list-scroll-padding-y | 0.25rem | Default: 0.25rem. |
| --combobox-popup-bg | var(--color-popover) | Default: var(--color-popover). |
| --combobox-popup-border-color | var(--color-border) | Default: var(--color-border). |
| --combobox-popup-max-height | 24rem | Default: 24rem. |
| --combobox-radius | var(--radius-md) | Default: var(--radius-md). |
| --combobox-separator-margin-x | 1rem | Default: 1rem. |
| --combobox-separator-margin-y | 0.375rem | Default: 0.375rem. |
| --combobox-shadow | var(--shadow-lg) | Default: var(--shadow-lg). |
| --combobox-status-color | var(--combobox-empty-color) | Default: var(--combobox-empty-color). |
| --combobox-status-font-size | var(--combobox-empty-font-size) | Default: var(--combobox-empty-font-size). |
| --combobox-status-gap | var(--spacing-2) | Default: var(--spacing-2). |
| --combobox-status-line-height | var(--combobox-empty-line-height) | Default: var(--combobox-empty-line-height). |
| --combobox-status-padding-x | var(--combobox-empty-padding-x) | Default: var(--combobox-empty-padding-x). |
| --combobox-status-padding-y | var(--combobox-empty-padding-y) | Default: var(--combobox-empty-padding-y). |
| --combobox-width | 16rem | Default: 16rem. |
Interactive variables scoped for docs preview without changing size scale tokens.
| Property | Value | Default | Description |
|---|---|---|---|
| --combobox-action-bg | transparent | Default: transparent. | |
| --combobox-action-bg-hover | var(--color-muted) | Default: var(--color-muted). | |
| --combobox-action-color-hover | var(--color-foreground) | Default: var(--color-foreground). | |
| --combobox-action-radius | var(--radius-sm) | Default: var(--radius-sm). | |
| --combobox-action-size | 1.5rem | Default: 1.5rem. | |
| --combobox-actions-gap | 0.125rem | Default: 0.125rem. | |
| --combobox-actions-offset-right | 0.5rem | Default: 0.5rem. | |
| --combobox-arrow-height | 0.625rem | Default: 0.625rem. | |
| --combobox-arrow-inline-offset | 13px | Default: 13px. | |
| --combobox-arrow-size | 8px | Default: 8px. | |
| --combobox-arrow-stroke-color | var(--combobox-popup-border-color) | Default: var(--combobox-popup-border-color). | |
| --combobox-arrow-width | 1.25rem | Default: 1.25rem. | |
| --combobox-backdrop-bg | var(--backdrop-bg, var(--color-overlay)) | Default: var(--backdrop-bg, var(--color-overlay)). | |
| --combobox-backdrop-blur | 2px | Default: 2px. | |
| --combobox-backdrop-transition | var(--transition-default) | Default: var(--transition-default). | |
| --combobox-bg | var(--color-background) | Default: var(--color-background). | |
| --combobox-border-color | var(--color-border) | Default: var(--color-border). | |
| --combobox-border-width | var(--border-width-sm) | Default: var(--border-width-sm). | |
| --combobox-check-padding-x-start | 0.625rem | Default: 0.625rem. | |
| --combobox-chip-bg | var(--color-muted) | Default: var(--color-muted). | |
| --combobox-chip-color | var(--color-foreground) | Default: var(--color-foreground). | |
| --combobox-chip-font-size | var(--text-sm) | Default: var(--text-sm). | |
| --combobox-chip-gap | var(--spacing-1) | Default: var(--spacing-1). | |
| --combobox-chip-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). | |
| --combobox-chip-min-height | 1.625rem | Default: 1.625rem. | |
| --combobox-chip-padding-left | 0.5rem | Default: 0.5rem. | |
| --combobox-chip-padding-right | 0.375rem | Default: 0.375rem. | |
| --combobox-chip-padding-y | 0.1875rem | Default: 0.1875rem. | |
| --combobox-chip-radius | var(--radius-sm) | Default: var(--radius-sm). | |
| --combobox-chip-remove-bg-hover | var(--color-overlay-foreground) | Default: var(--color-overlay-foreground). | |
| --combobox-chip-remove-icon-size | 0.75rem | Default: 0.75rem. | |
| --combobox-chip-remove-radius | var(--radius-sm) | Default: var(--radius-sm). | |
| --combobox-chip-remove-size | 1rem | Default: 1rem. | |
| --combobox-chips-gap | var(--spacing-1) | Default: var(--spacing-1). | |
| --combobox-chips-input-height | 1.75rem | Default: 1.75rem. | |
| --combobox-chips-input-min-width | 4rem | Default: 4rem. | |
| --combobox-chips-input-padding-x | 0.5rem | Default: 0.5rem. | |
| --combobox-chips-padding | var(--spacing-1) | Default: var(--spacing-1). | |
| --combobox-color | var(--color-foreground) | Default: var(--color-foreground). | |
| --combobox-control-height | var(--size-lg) | Default: var(--size-lg). | |
| --combobox-empty-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). | |
| --combobox-empty-font-size | var(--text-sm) | Default: var(--text-sm). | |
| --combobox-empty-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). | |
| --combobox-empty-padding-x | 1rem | Default: 1rem. | |
| --combobox-empty-padding-y | 0.75rem | Default: 0.75rem. | |
| --combobox-field-gap | 0.375rem | Default: 0.375rem. | |
| --combobox-focus-ring-color | var(--color-ring) | Default: var(--color-ring). | |
| --combobox-group-label-bg | var(--color-popover) | Default: var(--color-popover). | |
| --combobox-group-label-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). | |
| --combobox-group-label-font-size | var(--text-xs) | Default: var(--text-xs). | |
| --combobox-group-label-font-weight | var(--weight-semibold) | Default: var(--weight-semibold). | |
| --combobox-group-label-line-height | var(--line-height-text-xs) | Default: var(--line-height-text-xs). | |
| --combobox-group-label-padding-bottom | 0.35rem | Default: 0.35rem. | |
| --combobox-group-label-padding-top | 0.35rem | Default: 0.35rem. | |
| --combobox-group-label-padding-x | 0.625rem | Default: 0.625rem. | |
| --combobox-group-padding-bottom | var(--spacing-1) | Default: var(--spacing-1). | |
| --combobox-highlight-bg | var(--color-foreground) | Default: var(--color-foreground). | |
| --combobox-highlight-color | var(--color-background) | Default: var(--color-background). | |
| --combobox-highlight-inset-x | var(--spacing-1) | Default: var(--spacing-1). | |
| --combobox-highlight-radius | var(--radius-sm) | Default: var(--radius-sm). | |
| --combobox-icon-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). | |
| --combobox-icon-size | 0.875rem | Default: 0.875rem. | |
| --combobox-icon-svg-size | 1rem | Default: 1rem. | |
| --combobox-inline-input-container-padding-bottom | var(--spacing-2) | Default: var(--spacing-2). | |
| --combobox-inline-input-container-padding-top | var(--spacing-2) | Default: var(--spacing-2). | |
| --combobox-inline-input-container-padding-x | var(--spacing-2) | Default: var(--spacing-2). | |
| --combobox-inline-input-divider-color | var(--combobox-border-color) | Default: var(--combobox-border-color). | |
| --combobox-inline-input-divider-style | solid | Default: solid. | |
| --combobox-inline-input-divider-width | var(--border-width-sm) | Default: var(--border-width-sm). | |
| --combobox-input-group-padding-x | 0 | Default: 0. | |
| --combobox-input-padding-x-end | 3.25rem | Default: 3.25rem. | |
| --combobox-input-padding-x-start | 0.875rem | Default: 0.875rem. | |
| --combobox-input-placeholder-color | var(--color-muted-foreground) | Default: var(--color-muted-foreground). | |
| --combobox-item-bg | transparent | Default: transparent. | |
| --combobox-item-border-color | transparent | Default: transparent. | |
| --combobox-item-border-radius | 0 | Default: 0. | |
| --combobox-item-border-width | 0 | Default: 0. | |
| --combobox-item-color | var(--color-foreground) | Default: var(--color-foreground). | |
| --combobox-item-font-size | var(--text-sm) | Default: var(--text-sm). | |
| --combobox-item-gap | var(--spacing-2) | Default: var(--spacing-2). | |
| --combobox-item-indicator-bg | transparent | Default: transparent. | |
| --combobox-item-indicator-border-color | transparent | Default: transparent. | |
| --combobox-item-indicator-border-width | 0 | Default: 0. | |
| --combobox-item-indicator-icon-size | 0.75rem | Default: 0.75rem. | |
| --combobox-item-indicator-padding | 0 | Default: 0. | |
| --combobox-item-indicator-radius | 0 | Default: 0. | |
| --combobox-item-indicator-size | 0.75rem | Default: 0.75rem. | |
| --combobox-item-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). | |
| --combobox-item-min-height | 2rem | Default: 2rem. | |
| --combobox-item-padding-x-end | 1rem | Default: 1rem. | |
| --combobox-item-padding-y | var(--spacing-2) | Default: var(--spacing-2). | |
| --combobox-item-text-content-gap | var(--spacing-2) | Default: var(--spacing-2). | |
| --combobox-item-text-icon-color | currentColor | Default: currentColor. | |
| --combobox-item-text-icon-size | 1rem | Default: 1rem. | |
| --combobox-label-font-size | var(--text-sm) | Default: var(--text-sm). | |
| --combobox-label-font-weight | var(--weight-medium) | Default: var(--weight-medium). | |
| --combobox-label-line-height | var(--line-height-text-sm) | Default: var(--line-height-text-sm). | |
| --combobox-list-max-height | var(--combobox-popup-max-height) | Default: var(--combobox-popup-max-height). | |
| --combobox-list-padding-y | 0.25rem | Default: 0.25rem. | |
| --combobox-list-scroll-padding-y | 0.25rem | Default: 0.25rem. | |
| --combobox-popup-bg | var(--color-popover) | Default: var(--color-popover). | |
| --combobox-popup-border-color | var(--color-border) | Default: var(--color-border). | |
| --combobox-popup-max-height | 24rem | Default: 24rem. | |
| --combobox-radius | var(--radius-md) | Default: var(--radius-md). | |
| --combobox-separator-margin-x | 1rem | Default: 1rem. | |
| --combobox-separator-margin-y | 0.375rem | Default: 0.375rem. | |
| --combobox-shadow | var(--shadow-lg) | Default: var(--shadow-lg). | |
| --combobox-status-color | var(--combobox-empty-color) | Default: var(--combobox-empty-color). | |
| --combobox-status-font-size | var(--combobox-empty-font-size) | Default: var(--combobox-empty-font-size). | |
| --combobox-status-gap | var(--spacing-2) | Default: var(--spacing-2). | |
| --combobox-status-line-height | var(--combobox-empty-line-height) | Default: var(--combobox-empty-line-height). | |
| --combobox-status-padding-x | var(--combobox-empty-padding-x) | Default: var(--combobox-empty-padding-x). | |
| --combobox-status-padding-y | var(--combobox-empty-padding-y) | Default: var(--combobox-empty-padding-y). | |
| --combobox-width | 16rem | Default: 16rem. |
Anatomy
Combobox combines an input-like control with a popup list. Keep the field parts together
inside ComboboxField and keep list-related parts inside ComboboxContent so keyboard focus,
selection state, and filtering stay synchronized.
Combobox
├─ ComboboxField
│ ├─ ComboboxFieldLabel
│ └─ ComboboxInputGroup
│ ├─ ComboboxInput
│ └─ ComboboxControlActions
│ ├─ ComboboxClear
│ └─ ComboboxTrigger
└─ ComboboxContent
├─ service slots (internal): portal, backdrop, positioner, arrow
├─ ComboboxStatus / ComboboxEmpty
└─ ComboboxList
├─ ComboboxGroup / ComboboxCollection
├─ ComboboxRow (for grid lists)
├─ ComboboxSeparator
└─ ComboboxItem[value]
├─ ComboboxItemIndicator
└─ ComboboxItemText<Combobox items={fruits} itemToStringLabel={(item) => item.label}>
<ComboboxField>
<ComboboxFieldLabel htmlFor={id}>Choose fruit</ComboboxFieldLabel>
<ComboboxInputGroup>
<ComboboxInput id={id} placeholder="e.g. Mango" />
<ComboboxControlActions>
<ComboboxClear aria-label="Clear selection" />
<ComboboxTrigger aria-label="Open options" />
</ComboboxControlActions>
</ComboboxInputGroup>
</ComboboxField>
<ComboboxContent>
<ComboboxEmpty>No fruits found.</ComboboxEmpty>
<ComboboxList>
{(item) => (
<ComboboxItem key={item.id} value={item}>
<ComboboxItemIndicator />
<ComboboxItemText>{item.label}</ComboboxItemText>
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>| Part | Role |
|---|---|
Combobox | Root state machine. Controls selected value, input text, popup open state, filtering, and keyboard navigation. |
ComboboxField | Field wrapper for label and input area. Keep visible form-control pieces here for layout and accessibility semantics. |
ComboboxInput | Search/input control. Drives filtering and exposes input events, placeholder, and focus behavior. |
ComboboxControlActions | Slot for inline controls such as clear and trigger buttons. |
ComboboxContent | Popup surface with list/status/empty content. Also configures internal service layers and popup behavior props. |
ComboboxList | Collection renderer for options or grouped options. Contains ComboboxItem, ComboboxGroup, and ComboboxCollection. |
ComboboxItem | One selectable option. Renders selected state and text/indicator subparts. |
ComboboxChips | Multiple-selection chip container used with multiple and ComboboxValue. |
In most cases, keep default popup infrastructure and style the visible parts (ComboboxInput,
ComboboxItem, ComboboxItemIndicator, ComboboxItemText, ComboboxContent).
Use ComboboxContent service-slot props (classNames, portalProps, positionerProps,
backdropProps, arrowProps, withBackdrop, arrow) only when you need custom portal,
layering, backdrop, or arrow behavior.
Composition
Use Combobox for root state and behavior props such as value, defaultValue,
onValueChange, inputValue, onInputValueChange, open, onOpenChange, multiple,
filter, filteredItems, limit, grid, virtualized, modal, openOnInputClick,
autoHighlight, loopFocus, itemToStringLabel, itemToStringValue, and
isItemEqualToValue.
The moduix wrapper keeps Base UI behavior available while hiding service elements from the recommended composition:
| Base UI capability | moduix API |
|---|---|
| Root state, filtering, forms, grid mode | Pass Base UI root props directly to Combobox. |
| Flat items | Pass items to Combobox and render items with a function child on ComboboxList. |
| Grouped items | Pass grouped items, then render ComboboxGroup, ComboboxGroupLabel, and ComboboxCollection. |
| Async or remote search | Pass filter={null}, manage items, and announce progress with ComboboxStatus. |
| Externally filtered items | Pass filteredItems or combine useComboboxFilter with application state. |
| Virtualized lists | Pass virtualized, render custom rows in ComboboxList, and read useComboboxFilteredItems. |
| Multiple values | Pass multiple and render chips through ComboboxValue, ComboboxChips, and ComboboxChip. |
| Popup placement | Pass side, align, offsets, collision props, and container to ComboboxContent. |
| Portal, backdrop, positioner, arrow | Use ComboboxContent props instead of composing those service slots manually. |
className styles the visible popup. classNames styles the internal service slots that are
hidden from the default composition. Use container, portalProps, backdropProps,
positionerProps, arrowProps, withBackdrop, and arrow when you need the matching
Base UI escape hatches:
<ComboboxContent
className={styles.popup}
withArrow
withBackdrop
classNames={{
portal: styles.portal,
backdrop: styles.backdrop,
positioner: styles.positioner,
arrow: styles.arrow,
}}
/>Every visible part accepts className and the Base UI render prop where the underlying primitive
supports it, so the component can be styled with CSS Modules, plain CSS, CSS-in-JS, cascade layers,
or design-system wrappers. Default icons are only fallbacks: pass children to ComboboxTrigger,
ComboboxIcon, ComboboxClear, ComboboxItemIndicator, and ComboboxChipRemove to use your own
icon set.
Examples
Indicator Right With Icon
Use indicator="end" when the selected indicator should be aligned to the end. Pass children to icon slots to use icons from your application.
import { Combobox, ComboboxField, ComboboxFieldLabel, ComboboxInputGroup, ComboboxInput, ComboboxControlActions, ComboboxClear, ComboboxTrigger, ComboboxContent, ComboboxList, ComboboxItem, ComboboxItemText, ComboboxItemTextContent, ComboboxItemTextIcon, InfoIcon, ComboboxItemTextLabel, ComboboxItemIndicator,} from "moduix";import { useId } from "react";export function IndicatorRightComboboxDemo() { const id = useId(); return ( <Combobox items={fruits} itemToStringLabel={(item) => item.label}> <ComboboxField> <ComboboxFieldLabel htmlFor={id}>Choose fruit</ComboboxFieldLabel> <ComboboxInputGroup> <ComboboxInput id={id} placeholder="e.g. Mango" /> <ComboboxControlActions> <ComboboxClear aria-label="Clear selection" /> <ComboboxTrigger aria-label="Open options" /> </ComboboxControlActions> </ComboboxInputGroup> </ComboboxField> <ComboboxContent> <ComboboxList> {(item) => ( <ComboboxItem key={item.id} value={item} indicator="end"> <ComboboxItemText> <ComboboxItemTextContent> <ComboboxItemTextIcon> <InfoIcon /> </ComboboxItemTextIcon> <ComboboxItemTextLabel>{item.label}</ComboboxItemTextLabel> </ComboboxItemTextContent> </ComboboxItemText> <ComboboxItemIndicator /> </ComboboxItem> )} </ComboboxList> </ComboboxContent> </Combobox> );}const fruits = [ { id: "apple", label: "Apple", value: "apple" }, { id: "banana", label: "Banana", value: "banana" }, { id: "mango", label: "Mango", value: "mango" },];Input Inside Popup
Render ComboboxInput inside the popup when the closed field should behave like a select trigger.
import { Combobox, ComboboxField, ComboboxFieldLabel, ComboboxTrigger, ComboboxValue, ComboboxIcon, ComboboxContent, ComboboxInlineInputContainer, ComboboxInput, ComboboxEmpty, ComboboxList, ComboboxItem, ComboboxItemIndicator, ComboboxItemText,} from "moduix";import styles from "./combobox.module.css";export function InputInsidePopupComboboxDemo() { return ( <Combobox items={countries} itemToStringLabel={(item) => item.label}> <ComboboxField> <ComboboxFieldLabel>Country</ComboboxFieldLabel> <ComboboxTrigger className={styles.triggerField}> <ComboboxValue placeholder="Select country" /> <ComboboxIcon /> </ComboboxTrigger> </ComboboxField> <ComboboxContent className={styles.popupWithInlineInput}> <ComboboxInlineInputContainer> <ComboboxInput className={styles.inlineInput} placeholder="Search country" /> </ComboboxInlineInputContainer> <ComboboxEmpty>No countries found.</ComboboxEmpty> <ComboboxList className={styles.listWithInlineInput}> {(item) => ( <ComboboxItem key={item.id} value={item}> <ComboboxItemIndicator /> <ComboboxItemText>{item.label}</ComboboxItemText> </ComboboxItem> )} </ComboboxList> </ComboboxContent> </Combobox> );}.triggerField { display: inline-flex; width: var(--combobox-width, 16rem); min-height: var(--combobox-control-height, var(--size-lg)); align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0 0.875rem; border: var(--border-width-sm) solid var(--combobox-border-color, var(--color-border)); border-radius: var(--combobox-radius, var(--radius-md)); background-color: var(--combobox-bg, var(--color-background)); color: var(--combobox-color, var(--color-foreground)); transition: background-color var(--transition-default), border-color var(--transition-default), outline-color var(--transition-default); &[data-popup-open] { background-color: var(--color-muted); } &:focus-visible { border-color: var(--combobox-focus-ring-color, var(--color-ring)); outline: var(--border-width-sm) solid var(--combobox-focus-ring-color, var(--color-ring)); outline-offset: calc(var(--border-width-sm) * -1); }}.popupWithInlineInput { --combobox-inline-input-container-height: calc( var(--combobox-control-height, var(--size-lg)) + var(--combobox-inline-input-container-padding-top, var(--spacing-2)) + var(--combobox-inline-input-container-padding-bottom, var(--spacing-2)) + var(--combobox-inline-input-divider-width, var(--border-width-sm)) );}.listWithInlineInput { max-height: min( calc( var(--combobox-list-max-height, var(--combobox-popup-max-height, 24rem)) - var(--combobox-inline-input-container-height, 0px) ), calc(var(--available-height) - var(--combobox-inline-input-container-height, 0px)) ); padding-top: 0.375rem; padding-bottom: 0.5rem; scroll-padding-top: 0.375rem; scroll-padding-bottom: 0.5rem;}.inlineInput { padding-inline: 0.75rem; border: var(--border-width-sm) solid var(--combobox-border-color, var(--color-border)); border-radius: var(--radius-sm);}const countries = [ { id: "de", label: "Germany", value: "germany" }, { id: "es", label: "Spain", value: "spain" }, { id: "fr", label: "France", value: "france" },];Grouped
Pass grouped data to Combobox and render ComboboxCollection inside each ComboboxGroup.
import { Combobox, ComboboxField, ComboboxFieldLabel, ComboboxInputGroup, ComboboxInput, ComboboxControlActions, ComboboxClear, ComboboxTrigger, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxGroup, ComboboxGroupLabel, ComboboxCollection, ComboboxItem, ComboboxItemIndicator, ComboboxItemText,} from "moduix";import { useId } from "react";export function GroupedComboboxDemo() { const id = useId(); return ( <Combobox items={groupedProduce} itemToStringLabel={(item) => item.label}> <ComboboxField> <ComboboxFieldLabel htmlFor={id}>Select produce</ComboboxFieldLabel> <ComboboxInputGroup> <ComboboxInput id={id} placeholder="e.g. Spinach" /> <ComboboxControlActions> <ComboboxClear aria-label="Clear selection" /> <ComboboxTrigger aria-label="Open groups" /> </ComboboxControlActions> </ComboboxInputGroup> </ComboboxField> <ComboboxContent> <ComboboxEmpty>No produce found.</ComboboxEmpty> <ComboboxList> {(group) => ( <ComboboxGroup key={group.value} items={group.items}> <ComboboxGroupLabel>{group.value}</ComboboxGroupLabel> <ComboboxCollection> {(item) => ( <ComboboxItem key={item.id} value={item}> <ComboboxItemIndicator /> <ComboboxItemText>{item.label}</ComboboxItemText> </ComboboxItem> )} </ComboboxCollection> </ComboboxGroup> )} </ComboboxList> </ComboboxContent> </Combobox> );}const groupedProduce = [ { value: "Fruits", items: [ { id: "fruit-apple", label: "Apple", value: "apple" }, { id: "fruit-mango", label: "Mango", value: "mango" }, ], }, { value: "Vegetables", items: [ { id: "veg-carrot", label: "Carrot", value: "carrot" }, { id: "veg-spinach", label: "Spinach", value: "spinach" }, ], },];Multiple
Use the multiple prop and render chips inside ComboboxValue. The input remains the main control, so a separate trigger icon is optional.
import { Combobox, ComboboxField, ComboboxFieldLabel, ComboboxInputGroup, ComboboxChips, ComboboxValue, ComboboxChip, ComboboxChipText, ComboboxChipRemove, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxItem, ComboboxItemIndicator, ComboboxItemText,} from "moduix";import { useId } from "react";export function MultipleComboboxDemo() { const id = useId(); return ( <Combobox items={fruits} itemToStringLabel={(item) => item.label} multiple> <ComboboxField> <ComboboxFieldLabel htmlFor={id}>Select fruits</ComboboxFieldLabel> <ComboboxInputGroup> <ComboboxChips> <ComboboxValue> {(value) => ( <> {value.map((item) => ( <ComboboxChip key={item.id} aria-label={item.label}> <ComboboxChipText>{item.label}</ComboboxChipText> <ComboboxChipRemove aria-label={`Remove ${item.label}`} /> </ComboboxChip> ))} <ComboboxChipsInput id={id} placeholder={value.length === 0 ? "Select..." : ""} /> </> )} </ComboboxValue> </ComboboxChips> </ComboboxInputGroup> </ComboboxField> <ComboboxContent> <ComboboxEmpty>No fruits found.</ComboboxEmpty> <ComboboxList> {(item) => ( <ComboboxItem key={item.id} value={item}> <ComboboxItemIndicator /> <ComboboxItemText>{item.label}</ComboboxItemText> </ComboboxItem> )} </ComboboxList> </ComboboxContent> </Combobox> );}const fruits = [ { id: "apple", label: "Apple", value: "apple" }, { id: "banana", label: "Banana", value: "banana" }, { id: "mango", label: "Mango", value: "mango" },];Async Search
Disable the built-in filter with filter={null} when results are loaded from your application state or a remote API.
import { Combobox, ComboboxField, ComboboxFieldLabel, ComboboxInputGroup, ComboboxInput, ComboboxControlActions, ComboboxClear, ComboboxTrigger, ComboboxContent, ComboboxStatus, ComboboxEmpty, ComboboxList, ComboboxItem, ComboboxItemIndicator, ComboboxItemText, useComboboxFilter,} from "moduix";import { useId, useMemo, useRef, useState, useTransition } from "react";export function AsyncSearchComboboxDemo() { const id = useId(); const { contains } = useComboboxFilter(); const [searchResults, setSearchResults] = useState([]); const [selectedValue, setSelectedValue] = useState(null); const [searchValue, setSearchValue] = useState(""); const [isPending, startTransition] = useTransition(); const abortControllerRef = useRef(null); const trimmedSearchValue = searchValue.trim(); const items = useMemo(() => { if (!selectedValue || searchResults.some((user) => user.id === selectedValue.id)) { return searchResults; } return [...searchResults, selectedValue]; }, [searchResults, selectedValue]); const status = isPending ? "Searching..." : trimmedSearchValue === "" ? selectedValue ? null : "Start typing to search people..." : searchResults.length === 0 ? `No matches for "${trimmedSearchValue}".` : null; return ( <Combobox items={items} itemToStringLabel={(user) => user.name} filter={null} onValueChange={(nextSelectedValue) => { setSelectedValue(nextSelectedValue); setSearchValue(""); }} onInputValueChange={(nextSearchValue, { reason }) => { setSearchValue(nextSearchValue); if (nextSearchValue === "") { abortControllerRef.current?.abort(); setSearchResults([]); return; } if (reason === "item-press") { return; } const controller = new AbortController(); abortControllerRef.current?.abort(); abortControllerRef.current = controller; startTransition(async () => { await new Promise((resolve) => setTimeout(resolve, 250)); if (controller.signal.aborted) { return; } setSearchResults( directoryUsers.filter( (user) => contains(user.name, nextSearchValue) || contains(user.username, nextSearchValue) || contains(user.email, nextSearchValue) || contains(user.title, nextSearchValue), ), ); }); }} > <ComboboxField> <ComboboxFieldLabel htmlFor={id}>Assign reviewer</ComboboxFieldLabel> <ComboboxInputGroup> <ComboboxInput id={id} placeholder="e.g. Michael" /> <ComboboxControlActions> <ComboboxClear aria-label="Clear selection" /> <ComboboxTrigger aria-label="Open options" /> </ComboboxControlActions> </ComboboxInputGroup> </ComboboxField> <ComboboxContent> <ComboboxStatus>{status}</ComboboxStatus> <ComboboxEmpty> {trimmedSearchValue !== "" && !isPending && searchResults.length === 0 ? "Try a different search term." : null} </ComboboxEmpty> <ComboboxList> {(user) => ( <ComboboxItem key={user.id} value={user}> <ComboboxItemIndicator /> <ComboboxItemText>{user.name}</ComboboxItemText> </ComboboxItem> )} </ComboboxList> </ComboboxContent> </Combobox> );}const directoryUsers = [ { id: "leslie-alexander", name: "Leslie Alexander", username: "leslie", email: "leslie.alexander@example.com", title: "Product Manager", }, { id: "michael-foster", name: "Michael Foster", username: "michael", email: "michael.foster@example.com", title: "Frontend Engineer", },];Custom Styles
Use ComboboxContent props for popup infrastructure. Portal, Positioner, Backdrop, and Arrow are rendered internally, so application code only configures behavior and service-slot classes.
import { Combobox, ComboboxField, ComboboxFieldLabel, ComboboxInputGroup, ComboboxInput, ComboboxControlActions, ComboboxClear, ComboboxTrigger, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxItem, ComboboxItemIndicator, ComboboxItemText,} from "moduix";import { useId } from "react";import styles from "./combobox.module.css";export function CustomStylesComboboxDemo() { const id = useId(); return ( <Combobox items={fruits} itemToStringLabel={(item) => item.label}> <ComboboxField> <ComboboxFieldLabel htmlFor={id}>Choose fruit</ComboboxFieldLabel> <ComboboxInputGroup> <ComboboxInput id={id} placeholder="e.g. Mango" /> <ComboboxControlActions> <ComboboxClear aria-label="Clear selection" /> <ComboboxTrigger aria-label="Open options" /> </ComboboxControlActions> </ComboboxInputGroup> </ComboboxField> <ComboboxContent className={styles.customPopup} sideOffset={8} withArrow withBackdrop classNames={{ portal: styles.portal, backdrop: styles.backdrop, positioner: styles.positioner, arrow: styles.arrow, }} > <ComboboxEmpty>No fruits found.</ComboboxEmpty> <ComboboxList> {(item) => ( <ComboboxItem key={item.id} value={item}> <ComboboxItemIndicator /> <ComboboxItemText>{item.label}</ComboboxItemText> </ComboboxItem> )} </ComboboxList> </ComboboxContent> </Combobox> );}.customPopup { --combobox-popup-bg: var(--color-background); --combobox-popup-border-color: var(--color-ring); --combobox-shadow: var(--shadow-xl);}.portal { isolation: isolate;}.positioner { z-index: var(--z-popup);}.backdrop { --combobox-backdrop-bg: var(--color-overlay); --combobox-backdrop-blur: 2px;}.arrow { --combobox-arrow-stroke-color: var(--color-border);}const fruits = [ { id: "apple", label: "Apple", value: "apple" }, { id: "banana", label: "Banana", value: "banana" }, { id: "mango", label: "Mango", value: "mango" },];