--- title: Combobox description: A combobox combines a text field with a filterable popup list of known options. category: Forms --- import { meta as ComboboxMeta, Default, Multiple, TriggerKindManual } from '@/content/stories/combobox'; import Canvas from '@/components/component-docs/canvas'; import Demo from '@/components/component-docs/demo'; export const config = { dependencies: { components: [Canvas, Demo], scripts: [ '../../components/component-docs/demo.script.tsx', '../../components/component-docs/canvas.script.tsx', '../../components/component-docs/controls.script.tsx', ], }, }; # 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 `` 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. ```tsx 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'; Cat Dog No results found. ``` 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 `` coordinates any light-DOM tree that matches its query contract. The `Rui*` helpers stamp these targets; they are not required. ```tsx import '@ecopages/radiant-ui/combobox'; import '@ecopages/radiant-ui/listbox';
``` ## 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. ```tsx ``` 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 (``) 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` | `` + `[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"` with `aria-expanded` reflecting popup state. - Multiple mode marks the popup listbox `aria-multiselectable="true"`; each known option toggles `aria-selected`. - Compose `RuiComboboxClear` for an explicit clear action; it restores focus to the input. - Associate a visible label via `RuiLabel` or `label`.