Listbox
An accessible listbox with single and multiple selection, grouping, and typeahead.
Features
- Single or multiple
- Groups and nesting
- Typeahead
- Controlled or uncontrolled
- Managed focus
Installation
npm install @ariaui/listbox
Examples
Three playgrounds from the legacy docs: a compact uncontrolled list with defaultValue, a controlled single-select with groups and a Listbox.Sub, and the same layout with selectionMode="multiple" plus a selection readout.
Styling uses semantic tokens via listboxExampleTokens.ts; checkmarks use @heroicons/react/24/solid. Controlled examples use exampleSource fences so hooks stay readable in the snippet.
Basic
Uncontrolled list with a default selected option.
No preview output yet.
Max visible items
Listbox with a fixed-height scroll region for a long option list.
No preview output yet.
Single selection with submenu
Grouped options with a nested submenu and controlled selection.
No preview output yet.
Multiple selection with submenu
Multi-select listbox with nested options and a live selection readout.
No preview output yet.
Anatomy
import * as Listbox from "@ariaui/listbox";
export default function Example() {
return (
<Listbox.Root>
<Listbox.Label />
<Listbox.Content>
<Listbox.Viewport>
<Listbox.Group>
<Listbox.GroupLabel />
<Listbox.Option />
</Listbox.Group>
<Listbox.Sub>
<Listbox.SubTrigger />
<Listbox.SubContent>
<Listbox.Option />
</Listbox.SubContent>
</Listbox.Sub>
</Listbox.Viewport>
</Listbox.Content>
</Listbox.Root>
);
}
API Reference
Root
Context provider and state container for the listbox. Coordinates selection and active-option behavior.
| Prop | Type | Default |
|---|---|---|
selectionMode | 'single' | 'multiple' | 'single' |
value | string | string[] | — |
defaultValue | string | string[] | — |
onValueChange | (value: string | string[]) => void | — |
Label
Accessible label for the listbox. Referenced by Content via aria-labelledby.
Content
The focusable listbox surface. Contains options and handles keyboard navigation.
| Attribute | Values |
|---|---|
| role | listbox |
| aria-labelledby | ID of the Label element |
| aria-multiselectable | true | false |
| aria-activedescendant | ID of the active option |
Viewport
Optional inner wrapper that caps visible height and uses native overflow scrolling for long lists.
| Prop | Type | Default |
|---|---|---|
maxVisibleItems* | number | — |
| Attribute | Values |
|---|---|
| [data-listbox-viewport] | Empty attribute for styling/testing |
Option
A selectable item in the listbox.
| Prop | Type | Default |
|---|---|---|
value* | string | — |
disabled | boolean | false |
| Attribute | Values |
|---|---|
| role | option |
| aria-selected | true | false |
| aria-disabled | true when disabled |
| [data-active] | true when focused/hovered |
Group
A group of related options. Associates with GroupLabel via aria-labelledby.
| Attribute | Values |
|---|---|
| role | group |
| aria-labelledby | ID of the GroupLabel |
GroupLabel
Label for an option group.
Sub
Container for a submenu composed of SubTrigger and SubContent.
| Prop | Type | Default |
|---|---|---|
offset | { x: number; y: number } | — |
SubTrigger
Trigger that opens a submenu on hover or ArrowRight.
| Attribute | Values |
|---|---|
| [data-state] | open | closed |
| [data-active] | true when focused/hovered |
SubContent
Submenu content surface containing nested options.
| Attribute | Values |
|---|---|
| [data-state] | open | closed |
Keyboard
| Shortcut | Action |
|---|---|
| ↓ | Move focus to the next option. Wraps to first. |
| ↑ | Move focus to the previous option. Wraps to last. |
| Home | Move focus to the first option. |
| End | Move focus to the last option. |
| Enter | Select the focused option. In multi-select, adds to the selection. |
| Space | Select the focused option. In multi-select, toggles selection. |
| → | Open a submenu when a SubTrigger is focused and move focus to its first item. |
| ← | Close a submenu and return focus to its SubTrigger. |
| Esc | Close an open submenu and return focus to its SubTrigger. |
| A-Z/0-9 | Typeahead — jump to the next option whose label starts with the typed characters (case-insensitive, 500ms buffer). |
Accessibility
The Listbox component implements the WAI-ARIA Listbox pattern using aria-activedescendant:
Contentrenders asrole="listbox"withtabindex="0"andaria-activedescendanttracking the active option.Optionrenders asrole="option"witharia-selectedreflecting the selection state.aria-multiselectable="true"is applied whenselectionMode="multiple".Grouprenders asrole="group"and is labelled by itsGroupLabel.- DOM focus stays on the listbox container during keyboard navigation.
- Disabled options expose
aria-disabled="true"and cannot be selected.