--- title: Select description: Selects let users choose one or more known values from a list inside a compact trigger and popup listbox. category: Forms --- import { meta as SelectMeta, Default, Multiple, Searchable, TriggerKindFocus } from '@/content/stories/select'; 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', ], }, }; # Select

Selects let users choose one or more known values from a list inside a compact trigger and popup listbox. Use a select when the value must be one of the options; use a combobox when users also need to type and filter.

## Try it ## Mental model `` is a behavior host: helpers place the trigger, value, and popup; the custom element owns open state, keyboard, and selection. Options live in an embedded `RuiListbox`. Named regions are Composition Helpers (`RuiSelectTrigger`, `RuiSelectClear`, …), 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 (`value="draft,published"`). 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, not characters inside a token. Not form-associated. Wrap in `RuiField` and read the value from `RuiForm` `onSubmit`. It does not appear in `new FormData(form)`. ## Usage Pass `options` for the default composition, or supply children for an explicit layout. ```tsx import { RuiSelect } from '@ecopages/radiant-ui/select'; import { RuiLabel } from '@ecopages/radiant-ui/label'; Animal ``` The `options` API does not include a clear control. Add `RuiSelectClear` in a composed layout when the field should be resettable. Children replace the default close icon. ```tsx import { RuiSelect, RuiSelectClear, RuiSelectControl, RuiSelectListbox, RuiSelectToggle, RuiSelectTrigger, RuiSelectValue, } from '@ecopages/radiant-ui/select'; import { RuiListbox, RuiListboxOption } from '@ecopages/radiant-ui/listbox'; import { RuiIconX } from '@ecopages/radiant-ui/icons'; Cat Dog ``` Always set `embedded` on a listbox inside the popup so the select owns selection and keyboard. ## 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/select'; import '@ecopages/radiant-ui/listbox';
``` BEM classes are presentation-only. Nested `rui-listbox` must have `embedded`. For multi-select chips, place `rui-tag-group` inside `[data-select-value]`. ## Multiple selection Set `selectionMode="multiple"`. The default `options` composition shows selected values as removable tags and adds a check indicator on each option. Composed layouts place a `RuiTagGroup` inside `RuiSelectValue`, and `RuiListboxOptionIndicator` inside an option to replace the check. ```tsx ``` The popup stays open while options are toggled. Pass `shouldCloseOnSelect` to close after each choice. ## Searchable lists Wrap the embedded listbox in `RuiAutocomplete` and add `RuiSelectSearch` when the collection is long. Opening the popup moves focus into the search field. ## Trigger kind `triggerKind` controls what opens the listbox (same attribute name as `RuiCombobox`; select supports only `focus` and `manual`): | Value | Opens when | | --- | --- | | `manual` (default) | Click, toggle, or arrow keys on the trigger | | `focus` | The trigger receives focus (plus click, toggle, and arrow keys) | ## Theming Select surfaces map to **semantic surface roles**, never Tailwind palette steps. Override at the theme layer, not in component CSS. | Part | CSS roles | | --- | --- | | Trigger (`rui-select__trigger`) | `on-background`; `border` on the control row | | Control row (`rui-select__control`) | `border`, `background`, `rounded-control` | | Popup (`rui-select__listbox`) | `popover` surface + `shadow-overlay` | | Selected value / toggle | `on-surface` | Geometry shares control tokens (`--size-control-*`, `--radius-control`, `--space-control-*`) with `RuiInput` and `RuiButton`. Customization of icons is through Composition Helper **children**, not CSS variables: pass children to `RuiSelectToggle`, `RuiSelectClear`, and `RuiListboxOptionIndicator`. ## Accessibility - The trigger exposes `role="combobox"`, `aria-expanded`, and `aria-haspopup="listbox"`. - Multiple mode marks the popup listbox `aria-multiselectable="true"`. - `RuiSelectClear` needs an accessible name (`aria-label="Clear selection"` by default). Clicking it clears the value and returns focus to the trigger. - Provide a visible label. The placeholder is not a substitute. ## API `RuiSelect` is a custom element (``) wrapping a button trigger and an embedded `RuiListbox`. 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 associated `RuiLabel`. | | `placeholder` | `string` | `''` | Shown when nothing is selected. | | `disabled` | `boolean` | `false` | Disabled state. | | `selection-mode` | `single` · `multiple` | `single` | Single or multi-select. | | `should-close-on-select` | `boolean` | | Whether selecting closes the popup (defaults to `true` for single, `false` for multiple). | | `trigger-kind` | `focus` · `manual` | `manual` | Controls what opens the listbox. | ### Light-DOM contract | Target | Required | Host writes | Author owns | | --- | --- | --- | --- | | `[data-ref="root"]` | yes | — | shell for popover anchoring | | `[data-select-trigger]` | yes | `role`, `aria-expanded`, `aria-controls`, `aria-haspopup`, `id`, `tabIndex`, `aria-disabled` | combobox `div` (not a native `