Select
A headless, accessible select built on the WAI-ARIA Listbox pattern with single/multiple selection and nested submenus.
Features
- Single or multiple selection
- Controlled or uncontrolled open state
- Grouped options
- Nested submenus
- Typeahead navigation
- Dismissible popover
Installation
npm install @ariaui/select
Examples
Three playgrounds ported from the legacy doc: uncontrolled single-select (with and without a default), controlled single- and multi-select with groups plus a nested submenu, and uncontrolled multi-select with a default array.
Styling uses shared class strings from selectExampleTokens.ts and @heroicons/react/24/solid icons.
Uncontrolled
An uncontrolled select with an initial value.
No preview output yet.
Disabled
Disabled select: disabled on the root locks the trigger (Pro Blocks-style “Disabled” placeholder label).
No preview output yet.
With icon
Leading icon + chevron trigger and icon + label rows in the panel (chart-style options; Pro Blocks “With Icon”).
No preview output yet.
Large list + scroll area
A long flat option list capped with ScrollArea viewport height (~seven visible rows) for the Pro Blocks “large list” pattern.
No preview output yet.
Grouped With Submenu
A controlled single-select with grouped options and a nested submenu.
No preview output yet.
Grouped Multiple With Submenu
A controlled multi-select with grouped options and a nested submenu.
No preview output yet.
Multiple Selection (Uncontrolled)
An uncontrolled multi-select with default values and a trigger summary of selected labels.
No preview output yet.
Framer Motion
Use Content asChild and SubContent asChild to slot select listbox props onto Framer Motion surfaces.
No preview output yet.
Framer Motion + Scroll Area
A Select dropdown using the large-list ScrollArea pattern as a standalone example.
No preview output yet.
Anatomy
import * as Select from "@ariaui/select";
export default function Example() {
return (
<Select.Root>
<Select.Trigger />
<Select.Content>
<Select.Label />
<Select.Group>
<Select.GroupLabel />
<Select.Option />
</Select.Group>
<Select.Sub>
<Select.SubTrigger />
<Select.SubContent>
<Select.Option />
</Select.SubContent>
</Select.Sub>
</Select.Content>
</Select.Root>
);
}
API Reference
Root
Context provider and state container for the select. Coordinates open state, selection, and the floating content position.
| Prop | Type | Default |
|---|---|---|
selectionMode | 'single' | 'multiple' | 'single' |
value | string | string[] | — |
defaultValue | string | string[] | — |
onValueChange | (value: string | string[]) => void | — |
open | boolean | — |
defaultOpen | boolean | false |
onOpenChange | (open: boolean) => void | — |
offset | { x: number; y: number } | { x: 0, y: 0 } |
disabled | boolean | false |
Trigger
Button that opens the select. Exposes the current state for styling and aria.
| Attribute | Values |
|---|---|
| aria-haspopup | 'listbox' |
| aria-expanded | true | false |
| aria-controls | id of the Content element |
| data-state | 'open' | 'closed' |
Content
Floating surface containing Options, Groups, and nested submenus. Portaled and positioned relative to Trigger.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
| Attribute | Values |
|---|---|
| role | 'listbox' |
| aria-multiselectable | true | false |
| aria-activedescendant | id of the active Option |
| data-state | 'open' | 'closed' |
Label
Accessible label for the select. Referenced by Content via aria-labelledby.
Option
A selectable item inside Content.
| Prop | Type | Default |
|---|---|---|
value* | string | — |
disabled | boolean | false |
asChild | boolean | false |
| Attribute | Values |
|---|---|
| role | 'option' |
| aria-selected | true | false |
| aria-disabled | true when disabled |
| data-active | true when focused / highlighted |
Group
Groups related Options. Associates with GroupLabel via aria-labelledby.
| Attribute | Values |
|---|---|
| role | 'group' |
| aria-labelledby | id of the GroupLabel |
GroupLabel
Label for a Group of Options.
Sub
Container for a submenu composed of SubTrigger and SubContent.
| Prop | Type | Default |
|---|---|---|
offset | { x: number; y: number } | { x: 0, y: 0 } |
open | boolean | — |
defaultOpen | boolean | false |
onOpenChange | (open: boolean) => void | — |
SubTrigger
Trigger that opens a nested submenu on hover or ArrowRight.
| Attribute | Values |
|---|---|
| data-state | 'open' | 'closed' |
| data-active | true when focused / highlighted |
SubContent
Submenu surface containing nested Options.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
| Attribute | Values |
|---|---|
| data-state | 'open' |
Keyboard
| Shortcut | Action |
|---|---|
| Enter/Space/↓/↑ | Open Content with focus on the selected (or first) Option when the Trigger is focused. |
| ↓ | Move the active Option to the next enabled Option. |
| ↑ | Move the active Option to the previous enabled Option. |
| Home | Move the active Option to the first enabled Option. |
| End | Move the active Option to the last enabled Option. |
| → | Open the focused submenu via SubTrigger and move focus into it. |
| ← | Close the current submenu and return focus to its SubTrigger. |
| Enter/Space | Select the active Option (toggles in multiple mode) and close single-selection Content. |
| Esc | Close Content and return focus to Trigger. |
| Tab | Close Content and move focus to the next focusable element. |
Accessibility
Select follows the WAI-ARIA Listbox (collapsible) pattern:
Triggerexposesaria-haspopup="listbox",aria-expanded, andaria-controlspointing at Content.Contentrenders withrole="listbox"and manages active descendants viaaria-activedescendant— no per-option focus shuffling.aria-multiselectableis set automatically whenselectionMode="multiple", and each Option exposesaria-selected.- Options with
disabledcarryaria-disabled="true"and are skipped by keyboard navigation and selection. - Provide a label via
Labelor anaria-label/aria-labelledbyon Root so assistive tech can announce the field. - Nested
Submenus open with hover or ArrowRight, close with ArrowLeft / Escape, and return focus to the SubTrigger. - Content is portaled so it escapes ancestor clipping and stacking contexts; click-outside dismissal returns focus to Trigger when appropriate.