0.1.0

Listbox

Listboxes present a scrollable set of options with single or multiple selection. Standalone, they are an inline chooser. Inside select and combobox, they are the embedded option surface for the popup.

Try it

Cat
Dog

Mental model

<rui-listbox> is a behavior host: RuiListboxOption children stay in parent JSX. The custom element coordinates aria-selected, roving tabindex, and rui-change. When embedded is set, the parent (select or combobox) owns selection and keyboard; the listbox only paints options.

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.

Not form-associated. A standalone listbox wrapped in RuiField is available to RuiForm onSubmit only. An embedded listbox is the parent select or combobox's option surface, not a field control.

Usage

Add RuiListboxOption children with unique value props, or pass options for the default composition.

import { RuiListbox, RuiListboxOption } from '@ecopages/radiant-ui/listbox';
 
<RuiListbox value="cat" label="Animal">
  <RuiListboxOption value="cat">Cat</RuiListboxOption>
  <RuiListboxOption value="dog">Dog</RuiListboxOption>
</RuiListbox>
<RuiListbox
  value="cat"
  label="Animal"
  options={[
    { value: 'cat', label: 'Cat' },
    { value: 'dog', label: 'Dog' },
  ]}
/>

Custom markup

<rui-listbox> coordinates any light-DOM tree that matches its query contract. The Rui* helpers stamp these targets; they are not required.

import '@ecopages/radiant-ui/listbox';
 
<rui-listbox value="cat" label="Animal">
  <div role="listbox" class="rui-listbox rui-listbox--bordered">
    <div role="option" data-value="cat" class="rui-listbox__option">Cat</div>
    <div role="option" data-value="dog" class="rui-listbox__option">Dog</div>
  </div>
</rui-listbox>

BEM classes are presentation-only. Do not set aria-selected or tabIndex on options — the host owns those.

Embedded listboxes

Set embedded when the listbox lives inside a select or combobox popup. The parent then owns selection; border chrome is omitted so the popup can provide it. An embedded listbox is not a field control: place the parent select or combobox inside RuiField. bordered overrides that default.

Selection indicators

The options API adds the default check SVG only in multiple mode. Single-select uses the selected-option background alone. For a composed option in either mode, place RuiListboxOptionIndicator as a child and pass children to replace the check. Visibility follows aria-selected; the indicator is decorative.

import { RuiListboxOption, RuiListboxOptionIndicator } from '@ecopages/radiant-ui/listbox';
import { RuiIconCheck } from '@ecopages/radiant-ui/icons';
 
<RuiListboxOption value="cat" label="Cat">
  Cat
  <RuiListboxOptionIndicator>
    <RuiIconCheck />
  </RuiListboxOptionIndicator>
</RuiListboxOption>

Set label when the option contains decorative nodes (emoji, icons). Without it, the accessible name is the option's text minus the indicator subtree.

Multiple selection

selectionMode="multiple" exposes aria-multiselectable="true". Click or Space/Enter toggles the focused option. Arrow keys move focus without changing the selection.

Cat
Dog
<RuiListbox
  selectionMode="multiple"
  value={['apple', 'banana']}
  label="Favorite fruit"
  options={[
    { value: 'apple', label: 'Apple' },
    { value: 'banana', label: 'Banana' },
  ]}
/>

Theming

Listbox surfaces map to semantic surface and selection roles, never Tailwind palette steps.

PartCSS roles
List surface (rui-listbox)background
Bordered variantborder, radius-container
Optionon-background, radius-control, space-control-*
Option hoversurface-container-low
Active option (visual focus)surface-container, on-background
Selected optionprimary, on-primary
Focus ringfocus-ring

Override at the theme layer (tokens/presets/colors/*.css). Embedded listboxes inherit the popup surface from the parent shell. Indicator layout uses :has([data-listbox-option-indicator]); there is no public CSS variable for the check glyph — replace it with helper children.

API

RuiListbox is a custom element (<rui-listbox>) around a role="listbox" surface. It is not form-associated.

Attributes

AttributeTypeDefaultDescription
valuestringomittedComma-separated selected tokens. Empty selection removes the attribute. The JS property is string[].
labelstring''Accessible name for the list.
disabledbooleanfalseDisable all selection.
selection-modesingle · multiplesingleSingle or multiple selection.
embeddedbooleanfalseParent-owned listbox: no border chrome, selection handled by the parent.
borderedbooleanfollows embeddedOverride the border (true standalone, false embedded).

Light-DOM contract

TargetRequiredHost writesAuthor owns
[role="listbox"]yesid, aria-multiselectable (multiple mode)the list node
[role="option"]yesaria-selected, roving tabIndexdata-value, data-label, aria-disabled, hidden
data-valueper option—selection identity; fallback trimmed text
data-labelper option—accessible name when children include decorative nodes

Do not set aria-selected or tabIndex on options. Nested hosts: none.

Events

EventDetailDescription
rui-change{ value: string[] }Fired when an option is selected.

View helpers

ComponentTarget stampedNotes
RuiListbox<rui-listbox> + [role="listbox"] shellAccepts options or children.
RuiListboxOption[role="option"], data-value, data-label—
RuiListboxOptionIndicator[data-listbox-option-indicator]Decorative; not queried by the host.

CSS classes

ClassDescription
.rui-listboxScrollable option list surface (role="listbox").
.rui-listbox--borderedBordered standalone listbox.
.rui-listbox__optionSelectable list option (RuiListboxOption).
.rui-listbox__option-indicatorSelected-state indicator inside an option.

Theme roles

PartCSS variables consumed
List surface--background
Bordered variant--border, --radius-container
Option--on-background, --radius-control, --space-control-x, --space-control-y
Option hover / active--surface-container-low, --surface-container
Selected option--primary, --on-primary
Focus ring--focus-ring

Accessibility

  • Options expose role="option" with aria-selected for the current choice.
  • Multiple mode exposes aria-multiselectable="true". The visual indicator is decorative; selection remains on aria-selected.
  • Arrow keys move focus between options. In multiple mode they do not toggle; Space or Enter does.
  • Provide a label or external heading so users know what the list represents.