Listbox
Overview
Single and multi-selection with active-descendant navigation.
Pattern: Listbox. Status: stable.
When to use
- Single or multiple option selection with active-descendant navigation.
- Virtual or custom-rendered lists where the input focus should remain on the listbox container.
When not to use
- Editable autocomplete; use Combobox.
- Menu actions that do not represent selected values.
Import
ts
import { createListbox } from 'ui-headless-runtime';Controller creation
Create Listbox during component mount or setup, subscribe before rendering derived UI, and keep every cleanup returned by registrations or DOM binding.
Options
- Public options:
id,loop,selectionMode,defaultValue,getValue,onValueChange,subscribeValue. - Disabled state, display text, IDs, and optional submitted values belong to registered
ListboxOptionobjects.
Snapshot
- Snapshot fields:
activeId,ariaMultiselectable,controlled,id,options,role,selectedValues. - Each option snapshot contains
id,text,value,disabled,selected, androle.
Commands
- Component commands:
handleKeyDown,registerOption,select,setActive. - Multiple-mode toggling is requested through
select; there is no separate publictogglecommand.
Events
- Events:
beforeSelect,select,stateChange. - Payloads contain the selected option, resulting
selectedValues, and typed change details.
Change reasons
- Change reasons:
programmatic,pointer,keyboard,typeahead.
Controlled mode
Controlled listboxes emit a requested readonly string[]; single-select mode limits that array to at most one value. The controller waits for the consumer store to commit it.
Uncontrolled mode
Uncontrolled listboxes own selected value(s) and active option.
DOM binding
- Register options as they render; apply active-descendant metadata to the focusable listbox element.
Required markup
- Use
role="listbox"androle="option"with visible option text.
ARIA contract
- Apply
aria-selected,aria-disabled,aria-activedescendant, andaria-multiselectablefrom the snapshot.
Keyboard interaction
- ArrowDown / ArrowUp: Move the active item, skipping disabled items.
- Home / End: Move to the first or last enabled item.
- Enter / Space: Select the active option.
- Type characters: Move by normalized typeahead.
Focus behavior
- Focus remains on the listbox container while Arrow keys update the active option.
Nested behavior
- Listboxes are not overlay managers; pair with Popover only when you need anchored display.
Cleanup
- Dynamic option removal recalculates active item and selected values.
Minimal lifecycle example
ts
import { createListbox } from 'ui-headless-runtime';
const controller = createListbox();
const unsubscribe = controller.subscribe((snapshot) => {
console.log(snapshot);
});
console.log(controller.getSnapshot());
unsubscribe();
controller.destroy();The production demo loads the exact executable module from apps/demo/src/examples/listbox.ts.
Edge cases
single: One selected value.multiple: Selection toggles independently.disabled: Unavailable options are skipped.
Limitations
- Filtering and typed input are Combobox responsibilities.
Related links
API reference
See createListbox.