Listbox
A listbox presents a list of selectable options using the ARIA listbox role, with full keyboard navigation support. It is used when users need to select one or more items from a visible list, as opposed to a dropdown select. Common use cases include settings panels, filter selections, multi-select interfaces, and any scenario where all options should be visible simultaneously.
The component manages focus movement between options via arrow keys, with Home and End keys to jump to the first and last items. The consumer provides option elements as children and handles selection state externally.
Example
Show demo markup
<div class="listbox" role="listbox" aria-label="Options"><div role="option" aria-selected="true">Option A</div><div role="option">Option B</div></div>
Usage example
<script lang="ts">
import Listbox from "./Listbox.svelte";
</script>
<Listbox label="Fruits">
<div role="option" tabindex="-1">Apple</div>
<div role="option" tabindex="-1">Banana</div>
<div role="option" tabindex="-1">Cherry</div>
<div role="option" tabindex="-1">Durian</div>
</Listbox>
Show Svelte source
// In your Svelte component:
import Listbox from "lily-design-system-svelte-headless/components/Listbox/Listbox.svelte";
<Listbox>
<!-- content -->
</Listbox>
Usage
Priority level selector:
<Listbox label="Select priority">
<div role="option" tabindex="-1" aria-selected="true">High</div>
<div role="option" tabindex="-1" aria-selected="false">Medium</div>
<div role="option" tabindex="-1" aria-selected="false">Low</div>
</Listbox>
Department filter in a settings panel:
<Listbox label="Filter by department">
{#each departments as dept}
<div
role="option"
tabindex="-1"
aria-selected={selectedDept === dept}
onclick={() => selectedDept = dept}
>
{dept}
</div>
{/each}
</Listbox>
Theme selection with visible options:
<Listbox label="Choose a theme">
<div role="option" tabindex="-1" aria-selected="false">Light</div>
<div role="option" tabindex="-1" aria-selected="false">Dark</div>
<div role="option" tabindex="-1" aria-selected="true">System</div>
</Listbox>
Props
label: string (required) -- accessible name applied via aria-label.
children: slot (required) -- option elements to render inside the listbox (should have role="option" and tabindex="-1").
...restProps: unknown -- additional attributes spread onto the container <div>.
Keyboard Interactions
- ArrowDown: Moves focus to the next option, wrapping to the first option from the last.
- ArrowUp: Moves focus to the previous option, wrapping to the last option from the first.
- Home: Moves focus to the first option.
- End: Moves focus to the last option.
ARIA
role="listbox" -- identifies the container as a listbox widget for selecting from a list of options.
aria-label -- provides an accessible name describing the purpose of the listbox.
- Child elements should use
role="option" and optionally aria-selected to indicate selection state.
When to Use
- Use Listbox when users need to select one or more items from a visible list of options, such as filter panels or settings.
- Use Listbox instead of a Select when all options should be visible simultaneously without opening a dropdown.
- Use Listbox for preference or settings panels where the user chooses from a short to medium list.
- Use Listbox for multi-select interfaces where users need to see all available options at once.
When Not to Use
- Do not use Listbox for dropdown selection where space is limited -- use Select or Combobox instead.
- Do not use Listbox for checkable items with independent on/off states -- use CheckboxGroup instead.
- Do not use Listbox for very long option lists that benefit from type-ahead filtering -- use Combobox instead.
Headless
This headless component provides the ARIA listbox role, keyboard navigation (arrow keys, Home, End with wrapping), and focus management for child option elements. The consumer provides all visual styling, including option appearance, selected-state highlighting, scrolling behavior, and focus indicators.
Styles
The consumer provides all CSS styling. The component renders with a .listbox class for targeting. No default styles are included — this is a fully headless component.
Testing
- Verify the component renders a
<div> element with class listbox
- Verify role="listbox"` -- identifies the container as a listbox widget for selecting from a list of options.
- Verify aria-label` -- provides an accessible name describing the purpose of the listbox.
- Verify Child elements should use
role="option" and optionally aria-selected to indicate selection state.
- Verify keyboard interactions work correctly
- Verify pass-through attributes are applied
Advice
- Designers: Provide clear visual distinction for the selected and focused option states, using both color and a non-color indicator to support color-blind users.
- Developers: Ensure each child option uses
role="option" with tabindex="-1" and manage aria-selected on options to reflect the current selection state.
Implementation Notes
- Renders a
<div> with role="listbox" and an accessible label.
- Uses reactive state to track the
listRef element reference for querying child options.
- Keyboard navigation queries all child elements with
[role='option'] to build the navigable item list.
- Arrow keys wrap around: pressing ArrowDown on the last item moves focus to the first, and vice versa.
- Home and End keys jump to the first and last option respectively.
- Spreads
restProps onto the container for consumer customization.
Related components
select — a dropdown select element for choosing one option
combobox — a text input combined with a dropdown list for filtering options
option — an option element within a select dropdown
References
- WAI-ARIA Listbox Pattern: https://www.w3.org/WAI/ARIA/apg/patterns/listbox/
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: listbox
- PascalCase: Listbox
- Description: a list of selectable options with keyboard navigation
- Status: stable — exercised in composed page flows under e2e and axe, beyond the per-component checks
- HTML tag: <div>
- CSS class: .listbox
- Interactive: yes
ARIA
role="listbox" -- identifies the container as a listbox widget for selecting from a list of options. aria-label -- provides an accessible name describing the purpose of the listbox. - Child elements should use
role="option" and optionally aria-selected to indicate selection state.
Keyboard
- ArrowDown: Moves focus to the next option, wrapping to the first option from the last.
- ArrowUp: Moves focus to the previous option, wrapping to the last option from the first.
- Home: Moves focus to the first option.
- End: Moves focus to the last option.
Props
label: string (required) -- accessible name applied via aria-label. children: slot (required) -- option elements to render inside the listbox (should have role="option" and tabindex="-1"). ...restProps: unknown -- additional attributes spread onto the container <div>.