useListBox
Provides the behavior and accessibility implementation for a listbox component. A listbox displays a list of options and allows a user to select one or more of them.
install | yarn add @react-aria/listbox |
---|---|
version | 3.0.0-alpha.1 |
usage | import {useListBox, useOption, useListBoxSection} from '@react-aria/listbox' |
API#
useListBox<T>(props: AriaListBoxProps, state: ListState<T>): ListBoxAria
useOption<T>(props: OptionProps, state: ListState<T>): OptionAria
useListBoxSection(props: ListBoxSectionProps): ListBoxSectionAria
Features#
A listbox can be built using the <select>
and <option> HTML elements, but this is
not possible to style consistently cross browser. useListBox
helps achieve accessible
listbox components that can be styled as needed.
Note: useListBox
only handles the list itself. For a dropdown similar to a <select>
, see useSelect.
- Exposed to assistive technology as a
listbox
using ARIA - Support for single, multiple, or no selection
- Support for disabled items
- Support for sections
- Labeling support for accessibility
- Support for mouse, touch, and keyboard interactions
- Tab stop focus management
- Keyboard navigation support including arrow keys, home/end, page up/down, select all, and clear
- Automatic scrolling support during keyboard navigation
- Typeahead to allow focusing options by typing text
- Virtualized scrolling support for performance with long lists
Anatomy#
A listbox consists of a container element, with a list of options or groups inside.
useListBox
, useOption
, and useListBoxSection
handle exposing this to assistive
technology using ARIA, along with handling keyboard, mouse, and interactions to support
selection and focus behavior.
useListBox
returns props that you should spread onto the list container element:
Name | Type | Description |
listBoxProps | HTMLAttributes<HTMLElement> | Props for the listbox element. |
useOption
returns props for an individual option and its children:
Name | Type | Description |
optionProps | HTMLAttributes<HTMLElement> | Props for the option element. |
labelProps | HTMLAttributes<HTMLElement> | Props for the main text element inside the option. |
descriptionProps | HTMLAttributes<HTMLElement> | Props for the description text element inside the option, if any. |
useListBoxSection
returns props for a section:
Name | Type | Description |
itemProps | HTMLAttributes<HTMLElement> | Props for the wrapper list item. |
headingProps | HTMLAttributes<HTMLElement> | Props for the heading element, if any. |
groupProps | HTMLAttributes<HTMLElement> | Props for the group element. |
State is managed by the useListState
hook from @react-stately/list
. The state object should be passed as an option to
each of the above hooks.
If a listbox, options, or group does not have a visible label, an aria-label
or aria-labelledby
prop must be passed instead to identify the element to assisitive technology.
State management#
useListBox
requires knowledge of the options in the listbox in order to handle keyboard
navigation and other interactions. It does this using
the Collection
interface, which is a generic interface to access sequential unique keyed data. You can
implement this interface yourself, e.g. by using a prop to pass a list of item objects,
but useListState
from
@react-stately/list
implements a JSX based interface for building collections instead.
See Collection Components for more information,
and Collection Interface for internal details.
In addition, useListState
manages the state necessary for multiple selection and exposes
a SelectionManager
,
which makes use of the collection to provide an interface to update the selection state.
For more information, see Selection.
Example#
This example uses HTML <ul>
and <li>
elements to represent the list, and applies
props from useListBox
and useOption
.
import {useListState} from '@react-stately/list';
import {Item} from '@react-stately/collections';
import {useFocusRing} from '@react-aria/focus';
import {mergeProps} from '@react-aria/utils';
function ListBox(props) {
// Create state based on the incoming props
let state = useListState(props);
// Get props for the listbox element
let ref = ReactuseRef();
let {listBoxProps} = useListBox({...props ref} state);
return (
<ul
...listBoxProps
ref= ref
style={
padding: 0
listStyle: 'none'
border: '1px solid gray'
maxWidth: 250
}>
[...statecollection]map(item => (
<Option
key= itemkey
item= item
state= state />
))
</ul>
);
}
function Option({item state}) {
// Get props for the option element
let ref = ReactuseRef();
let isDisabled = statedisabledKeyshas(itemkey);
let isSelected = stateselectionManagerisSelected(itemkey);
let {optionProps} = useOption({
key: itemkey
ref
isDisabled
isSelected
} state);
// Determine whether we should show a keyboard
// focus ring for accessibility
let {isFocusVisible focusProps} = useFocusRing();
return (
<li
...mergeProps(optionProps focusProps)
ref= ref
style={
background: isSelected ? 'blueviolet' : 'transparent'
color: isSelected ? 'white' : null
padding: '2px 5px'
outline: isFocusVisible
? '2px solid orange'
: 'none'
}>
itemrendered
</li>
);
}
<ListBox>
<Item>One</Item>
<Item>Two</Item>
<Item>Three</Item>
</ListBox>
Sections#
This example shows how a listbox can support sections with separators and headings
using props from useListBoxSection
.
This is accomplished using four extra elements: an <li>
between the sections to
represent the separator, an <li>
to contain the heading <span>
element, and a
<ul>
to contain the child items. This structure is necessary to ensure HTML semantics
are correct.
import {Section} from '@react-stately/collections';
import {useSeparator} from '@react-aria/separator';
function ListBox(props) {
let state = useListState(props);
let ref = ReactuseRef();
let {listBoxProps} = useListBox({...props ref} state);
return (
<ul
...listBoxProps
ref= ref
style={
margin: 0
padding: 0
listStyle: 'none'
border: '1px solid gray'
maxWidth: 250
}>
[...statecollection]map(item => (
<ListBoxSection
key= itemkey
section= item
state= state />
))
</ul>
);
}
function ListBoxSection({section state}) {
let {itemProps headingProps groupProps} = useListBoxSection({
heading: sectionrendered
'aria-label': section['aria-label']
});
let {separatorProps} = useSeparator({
elementType: 'li'
});
// If the section is not the first, add a separator element.
// The heading is rendered inside an <li> element, which contains
// a <ul> with the child items.
return <>
sectionkey !== statecollectiongetFirstKey() &&
<li
...separatorProps
style={
borderTop: '1px solid gray'
margin: '2px 5px'
} />
<li ...itemProps>
sectionrendered &&
<span
...headingProps
style={
fontWeight: 'bold'
fontSize: '1.1em'
padding: '2px 5px'
}>
sectionrendered
</span>
<ul
...groupProps
style={
padding: 0
listStyle: 'none'
}>
[...sectionchildNodes]map(node =>
<Option
key= nodekey
item= node
state= state />
)
</ul>
</li>
</>;
}
function Option({item state}) {
// Same as in the first example...
}
<ListBox>
<Section title="Section 1">
<Item>One</Item>
<Item>Two</Item>
<Item>Three</Item>
</Section>
<Section title="Section 2">
<Item>One</Item>
<Item>Two</Item>
<Item>Three</Item>
</Section>
</ListBox>
Complex options#
By default, options that only contain text will be labelled by the contents of the option.
For options that have more complex content (e.g. icons, multiple lines of text, etc.), use
labelProps
and descriptionProps
from useOption
as needed to apply to the main text element of the option and its description. This improves screen
reader announcement.
NOTE: listbox options cannot contain interactive content (e.g. buttons, checkboxes, etc.).
For these cases, see useGrid
instead.
This example shows how labelProps
and descriptionProps
can be applied to child elements of
the item to apply ARIA properties returned by useOption
.
This is done using React.cloneElement
in this example, but you can use context or other
approaches for this as well.
function ListBox(props) {
// Same as the first example...
}
function Option({item state}) {
let ref = ReactuseRef();
let isDisabled = statedisabledKeyshas(itemkey);
let isSelected = stateselectionManagerisSelected(itemkey);
let {optionProps labelProps descriptionProps} = useOption({
key: itemkey
ref
isDisabled
isSelected
} state);
let {isFocusVisible focusProps} = useFocusRing();
// Pull out the two expected children. We will clone them
// and add the necessary props for accessibility.
let [title description] = itemrendered;
return (
<li
...mergeProps(optionProps focusProps)
ref= ref
style={
background: isSelected ? 'blueviolet' : 'transparent'
color: isSelected ? 'white' : null
padding: '2px 5px'
outline: isFocusVisible
? '2px solid orange'
: 'none'
}>
ReactcloneElement(title labelProps)
ReactcloneElement(description descriptionProps)
</li>
);
}
<ListBox>
<Item textValue="Align Left">
<div><strong>Align Left</strong></div>
<div>Align the selected text to the left</div>
</Item>
<Item textValue="Align Center">
<div><strong>Align Center</strong></div>
<div>Align the selected text center</div>
</Item>
<Item textValue="Align Right">
<div><strong>Align Right</strong></div>
<div>Align the selected text to the right</div>
</Item>
</ListBox>
Internationalization#
useListBox
handles some aspects of internationalization automatically.
For example, type to select is implemented with an
Intl.Collator
for internationalized string matching. You are responsible for localizing all labels and option
content that is passed into the listbox.
RTL#
In right-to-left languages, the listbox options should be mirrored. The text content should be aligned to the right. Ensure that your CSS accounts for this.
Name | Type | Description |
ref | RefObject<HTMLDivElement> | A ref to the listbox container element. |
isVirtualized | boolean | Whether the listbox uses virtual scrolling. |
keyboardDelegate | KeyboardDelegate | An optional keyboard delegate implementation for type to select, to override the default. |
autoFocus | boolean | FocusStrategy | Whether the auto focus the listbox or an option. |
shouldFocusWrap | boolean | Whether focus should wrap around when the end/start is reached. |
Name | Type | Description |
collection | Collection<Node<T>> | A collection of items in the list. |
disabledKeys | Set<Key> | A set of items that are disabled. |
selectionManager | SelectionManager | A selection manager to read and update multiple selection state. |
A generic interface to access a readonly sequential collection of unique keyed items.
Properties
Name | Type | Description |
size | number | The number of items in the collection. |
Methods
Method | Description |
getKeys(): Iterable<Key> | Iterate over all keys in the collection. |
getItem(key: Key): T | Get an item by its key. |
getKeyBefore(key: Key): Key | null | Get the key that comes before the given key in the collection. |
getKeyAfter(key: Key): Key | null | Get the key that comes after the given key in the collection. |
getFirstKey(): Key | null | Get the first key in the collection. |
getLastKey(): Key | null | Get the last key in the collection. |
An interface for reading and updating multiple selection state.
Properties
Name | Type | Description |
selectionMode | SelectionMode | The type of selection that is allowed in the collection. |
disallowEmptySelection | boolean | Whether the collection allows empty selection. |
isFocused | boolean | Whether the collection is currently focused. |
focusedKey | Key | The current focused key in the collection. |
selectedKeys | Set<Key> | The currently selected keys in the collection. |
isEmpty | any | Whether the selection is empty. |
isSelectAll | any | Whether all items in the collection are selected. |
Methods
Method | Description |
setFocused(isFocused: boolean): void | Sets whether the collection is focused. |
setFocusedKey(key: Key): void | Sets the focused key. |
isSelected(key: Key): void | Returns whether a key is selected. |
extendSelection(toKey: Key): void | Extends the selection to the given key. |
toggleSelection(key: Key): void | Toggles whether the given key is selected. |
replaceSelection(key: Key): void | Replaces the selection with only the given key. |
selectAll(): void | Selects all items in the collection. |
clearSelection(): void | Removes all keys from the selection. |
toggleSelectAll(): void | Toggles between select all and an empty selection. |
Name | Type | Description |
listBoxProps | HTMLAttributes<HTMLElement> | Props for the listbox element. |
Name | Type | Description |
isDisabled | boolean | Whether the option is disabled. |
isSelected | boolean | Whether the option is selected. |
aria-label | string | A screen reader only label for the option. |
key | Key | The unique key for the option. |
ref | RefObject<HTMLElement> | A ref to the option element. |
shouldSelectOnPressUp | boolean | Whether selection should occur on press up instead of press down. |
shouldFocusOnHover | boolean | Whether the option should be focused when the user hovers over it. |
isVirtualized | boolean | Whether the option is contained in a virtual scrolling listbox. |
Name | Type | Description |
optionProps | HTMLAttributes<HTMLElement> | Props for the option element. |
labelProps | HTMLAttributes<HTMLElement> | Props for the main text element inside the option. |
descriptionProps | HTMLAttributes<HTMLElement> | Props for the description text element inside the option, if any. |
Name | Type | Description |
heading | ReactNode | |
aria-label | string |
Name | Type | Description |
itemProps | HTMLAttributes<HTMLElement> | Props for the wrapper list item. |
headingProps | HTMLAttributes<HTMLElement> | Props for the heading element, if any. |
groupProps | HTMLAttributes<HTMLElement> | Props for the group element. |
Provides state management for list-like components. Handles building a collection of items from props, and manages multiple selection state.
useListState<T>(props: CollectionBase<T> & MultipleSelection): ListState<T>
Provides the behavior and accessibility implementation for a listbox component. A listbox displays a list of options and allows a user to select one or more of them.
useListBox<T>(props: AriaListBoxProps, state: ListState<T>): ListBoxAria
Provides the behavior and accessibility implementation for an option in a listbox.
See useListBox
for more details about listboxes.
useOption<T>(props: OptionProps, state: ListState<T>): OptionAria
Provides the behavior and accessibility implementation for a section in a listbox.
See useListBox
for more details about listboxes.
useListBoxSection(props: ListBoxSectionProps): ListBoxSectionAria