Combobox
A combobox combines a text field with a filterable popup list. Use it when users need to search a known set of options. Typed text filters the list; only selecting an option commits a value.
Try it
Mental model
<rui-combobox> is a behavior host: helpers place the input row and popup; the custom element owns filtering, keyboard, and selection. Options live in an embedded RuiListbox, usually wrapped in RuiAutocomplete. Named regions are Composition Helpers, not HTML slots.
The live value property is always a string[] ([] when nothing is selected). The HTML attribute is those tokens as a comma-separated string. JSX may pass a string or an array; a string is parsed as CSV. rui-change emits { value: string[] }. Commas in the attribute are delimiters.
Important: typing does not create a custom value. Enter with no active option closes the popup. Escape while closed clears the committed value and the input. If only predefined options are valid, that is already the runtime contract — do not expect free-text to be stored on value.
Not form-associated. Wrap in RuiField and read the selected tokens from RuiForm onSubmit. The filter textbox is not listed in new FormData(form).
Usage
Pass options for the default composition, or compose the helpers explicitly. Always set embedded on the listbox inside the popup.
import {
RuiCombobox,
RuiComboboxClear,
RuiComboboxControl,
RuiComboboxInput,
RuiComboboxListbox,
RuiComboboxTrigger,
} from '@ecopages/radiant-ui/combobox';
import { RuiAutocomplete, RuiAutocompleteCollection, RuiAutocompleteEmpty } from '@ecopages/radiant-ui/autocomplete';
import { RuiListbox, RuiListboxOption } from '@ecopages/radiant-ui/listbox';
import { RuiIconX } from '@ecopages/radiant-ui/icons';
<RuiCombobox value="cat" placeholder="Choose an animal">
<RuiComboboxControl>
<RuiComboboxInput />
<RuiComboboxClear aria-label="Clear animal selection">
<RuiIconX />
</RuiComboboxClear>
<RuiComboboxTrigger />
</RuiComboboxControl>
<RuiComboboxListbox>
<RuiAutocomplete>
<RuiAutocompleteCollection>
<RuiListbox embedded>
<RuiListboxOption value="cat">Cat</RuiListboxOption>
<RuiListboxOption value="dog">Dog</RuiListboxOption>
</RuiListbox>
<RuiAutocompleteEmpty>No results found.</RuiAutocompleteEmpty>
</RuiAutocompleteCollection>
</RuiAutocomplete>
</RuiComboboxListbox>
</RuiCombobox>Children on RuiComboboxClear and RuiComboboxTrigger replace the default SVGs. For decorated options, set label on RuiListboxOption so the committed input text stays clean of icons and indicators.
Custom markup
<rui-combobox> coordinates any light-DOM tree that matches its query contract. The Rui* helpers stamp these targets; they are not required.
import '@ecopages/radiant-ui/combobox';
import '@ecopages/radiant-ui/listbox';
<rui-combobox value="cat" placeholder="Choose an animal">
<div data-ref="root" class="rui-combobox">
<div class="rui-combobox__control">
<input type="text" data-combobox-input data-autocomplete-input />
<button type="button" data-combobox-trigger></button>
</div>
<div data-combobox-listbox hidden>
<rui-listbox embedded>
<div role="option" data-value="cat">Cat</div>
</rui-listbox>
</div>
</div>
</rui-combobox>Multiple selection
selectionMode="multiple" toggles known options and keeps the popup open. Selected options render as removable chips. Typed text only filters; it is not appended as a token.
<RuiCombobox
selectionMode="multiple"
value={['ca', 'ny']}
options={[
{ value: 'ca', label: 'California' },
{ value: 'ny', label: 'New York' },
]}
/>For composed layouts, place RuiComboboxValue (with a RuiTagGroup) before RuiComboboxInput. Add RuiListboxOptionIndicator inside an option to replace the selected checkmark.
Trigger kind
triggerKind controls what opens the listbox:
| Value | Opens when |
|---|---|
input (default) | The user types a matching query, or uses the trigger / arrow keys |
focus | The input receives focus (and while typing) |
manual | The trigger button or arrow keys only; typing filters without opening |
Theming
Combobox surfaces map to semantic surface roles, never Tailwind palette steps. Override at the theme layer.
| Part | CSS roles |
|---|---|
Control row (rui-combobox__control) | border, background, radius-control; focus-ring on focus-within |
| Input text | on-background, --text-control, --space-control-* |
| Disabled | opacity-muted |
Popup (rui-combobox__listbox) | popover surface + shadow-overlay (via rui-popover) |
Icon swaps are Composition Helper children, not CSS variables.
API
RuiCombobox is a custom element (<rui-combobox>) that coordinates a composed input row and listbox popup. It is not form-associated.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
value | string | omitted | Comma-separated selected tokens. Empty selection removes the attribute. The JS property is string[]. |
label | string | '' | Accessible name when there is no visible RuiLabel. |
placeholder | string | '' | Placeholder text for the input. |
disabled | boolean | false | Disable the input and trigger. |
selection-mode | single · multiple | single | Single or multi-select. |
should-close-on-select | boolean | Whether selection closes the popup (defaults to true for single, false for multiple). | |
trigger-kind | input · focus · manual | input | Controls what opens the listbox. |
Light-DOM contract
| Target | Required | Host writes | Author owns |
|---|---|---|---|
[data-ref="root"] | yes | — | shell for popover anchoring |
[data-combobox-input] | yes | role, aria-expanded, aria-controls, id, disabled | the input |
[data-combobox-listbox] | yes | hidden | popup shell |
[role="option"] | yes (in listbox) | aria-selected | data-value, label text |
[data-combobox-trigger] | no | aria-expanded, tabIndex="-1" | toggle button |
[data-combobox-clear] | no | hidden, disabled | clear button |
[data-combobox-value] | no | — | chip region before input |
Nested hosts: rui-listbox (embedded), rui-tag-group in [data-combobox-value], optional rui-autocomplete.
Events
| Event | Detail | Description |
|---|---|---|
rui-change | { value: string[] } | Fired when an option is selected, a chip is removed, clear runs, or Escape clears a closed field. |
View helpers
| Component | Target stamped | Notes |
|---|---|---|
RuiCombobox | <rui-combobox> + [data-ref="root"] | Accepts options or children |
RuiComboboxControl | — | Input row wrapper |
RuiComboboxInput | [data-combobox-input] | Also [data-autocomplete-input] |
RuiComboboxClear | [data-combobox-clear] | Hidden when empty |
RuiComboboxTrigger | [data-combobox-trigger] | Children replace chevron |
RuiComboboxValue | [data-combobox-value] | Before input in multiple mode |
RuiComboboxListbox | [data-combobox-listbox] | Place embedded RuiListbox inside |
CSS classes
| Class | Description |
|---|---|
.rui-combobox | Root surface. |
.rui-combobox__control | Bordered control-height row. |
.rui-combobox__input | Combobox text input. |
.rui-combobox__value | Selected-value chip region. |
.rui-combobox__listbox | Popup shell (adds rui-popover rui-popover--listbox rui-floating). |
Theme roles
| Part | CSS variables consumed |
|---|---|
| Control row | --border, --background, --radius-control, --focus-ring |
| Input text | --on-background, --text-control, --space-control-x, --space-control-y |
| Disabled | --opacity-muted |
| Popup | --shadow-overlay (via rui-popover) |
Accessibility
- The input exposes
role="combobox"witharia-expandedreflecting popup state. - Multiple mode marks the popup listbox
aria-multiselectable="true"; each known option togglesaria-selected. - Compose
RuiComboboxClearfor an explicit clear action; it restores focus to the input. - Associate a visible label via
RuiLabelorlabel.