useGridList
Provides the behavior and accessibility implementation for a list component with interactive children. A grid list displays data in a single column and enables a user to navigate its contents via directional navigation keys.
install | yarn add react-aria |
---|---|
version | 3.36.0 |
usage | import {useGridList, useGridListItem, useGridListSelectionCheckbox} from 'react-aria' |
API#
useGridList<T>(
props: AriaGridListOptions<T>,
state: ListState<T>,
ref: RefObject<HTMLElement
| | null>
): GridListAria
useGridListItem<T>(
props: AriaGridListItemOptions,
state: ListState<T>
| | TreeState<T>,
ref: RefObject<FocusableElement
| | null>
): GridListItemAria
useGridListSelectionCheckbox<T>(
(props: AriaGridSelectionCheckboxProps,
, state: ListState<T>
)): GridSelectionCheckboxAria
Features#
A list can be built using <ul> or <ol> HTML elements, but does not support any user interactions.
HTML lists are meant for static content, rather than lists with rich interactions like focusable elements within rows, keyboard navigation, row selection, etc.
useGridList
helps achieve accessible and interactive list components that can be styled as needed.
- Item selection – Single or multiple selection, with optional checkboxes, disabled rows, and both
toggle
andreplace
selection behaviors. - Interactive children – List items may include interactive elements such as buttons, checkboxes, menus, etc.
- Actions – Items support optional row actions such as navigation via click, tap, double click, or Enter key.
- Async loading – Support for loading items asynchronously, with infinite and virtualized scrolling.
- Keyboard navigation – List items and focusable children can be navigated using the arrow keys, along with page up/down, home/end, etc. Typeahead, auto scrolling, and selection modifier keys are supported as well.
- Touch friendly – Selection and actions adapt their behavior depending on the device. For example, selection is activated via long press on touch when item actions are present.
- Accessible – Follows the ARIA grid pattern, with additional selection announcements via an ARIA live region. Extensively tested across many devices and assistive technologies to ensure announcements and behaviors are consistent.
Note: Use useGridList
when your list items may contain interactive elements such as buttons, checkboxes, menus, etc. within them. If your list items contain only static content such as text and images, then consider using useListBox instead for a slightly better screen reader experience (especially on mobile).
Anatomy#
A grid list consists of a container element, with rows of data inside. The rows within a list may contain focusable elements or plain text content. If the list supports row selection, each row can optionally include a selection checkbox.
The useGridList
and useGridListItem
hooks handle keyboard, mouse, and other interactions to support
row selection, in list navigation, and overall focus behavior. Those hooks handle exposing the list and its contents to assistive technology using ARIA. useGridListSelectionCheckbox
handles row selection and associating each checkbox with its respective rows
for assistive technology.
useGridList
returns props that you should spread onto the list container element:
Name | Type | Description |
gridProps | DOMAttributes | Props for the grid element. |
useGridListItem
returns props for an individual option and its children, along with states you can use for styling:
Name | Type | Description |
rowProps | DOMAttributes | Props for the list row element. |
gridCellProps | DOMAttributes | Props for the grid cell element within the list row. |
descriptionProps | DOMAttributes | Props for the list item description element, if any. |
isPressed | boolean | Whether the item is currently in a pressed state. |
isSelected | boolean | Whether the item is currently selected. |
isFocused | boolean | Whether the item is currently focused. |
isDisabled | boolean | Whether the item is non-interactive, i.e. both selection and actions are disabled and the item may
not be focused. Dependent on |
allowsSelection | boolean | Whether the item may be selected, dependent on selectionMode , disabledKeys , and disabledBehavior . |
hasAction | boolean | Whether the item has an action, dependent on |
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 where applicable.
Note that an aria-label
or aria-labelledby
must be passed to the list to identify the element to assistive technology.
State management#
useGridList
requires knowledge of the rows in the list 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#
Lists are collection components that include rows as child elements.
In this example, we'll use the standard HTML unordered list elements along with hooks from React
Aria for each child. You may also use other elements like <div>
to render these components as appropriate.
We'll walk through creating the list container and list item, then add some additional behavior such as selection.
The useGridList
hook will be used to render the outer most list element. It uses
the useListState
hook to construct the list's collection of rows,
and manage state such as the focused row and row selection. We'll use the collection to iterate through
the rows of the List and render the relevant components, which we'll define below.
You may notice the extra <div>
with gridCellProps
in our example. This is needed because we are following the ARIA grid pattern, which does not allow rows without any child gridcell
elements.
import {mergeProps, useFocusRing, useGridList, useGridListItem} from 'react-aria';
import {useListState} from 'react-stately';
import {useRef} from 'react';
function List(props) {
let state = useListState(props);
let ref = useRef<HTMLUListElement | null>(null);
let { gridProps } = useGridList(props, state, ref);
return (
<ul {...gridProps} ref={ref} className="list">
{[...state.collection].map((item) => (
<ListItem key={item.key} item={item} state={state} />
))}
</ul>
);
}
function ListItem({ item, state }) {
let ref = React.useRef(null);
let { rowProps, gridCellProps, isPressed } = useGridListItem(
{ node: item },
state,
ref
);
let { isFocusVisible, focusProps } = useFocusRing();
let showCheckbox = state.selectionManager.selectionMode !== 'none' &&
state.selectionManager.selectionBehavior === 'toggle';
return (
<li
{...mergeProps(rowProps, focusProps)}
ref={ref}
className={` `}
>
<div {...gridCellProps}>
{showCheckbox && <ListCheckbox item={item} state={state} />}
{item.rendered}
</div>
</li>
);
}
import {
mergeProps,
useFocusRing,
useGridList,
useGridListItem
} from 'react-aria';
import {useListState} from 'react-stately';
import {useRef} from 'react';
function List(props) {
let state = useListState(props);
let ref = useRef<HTMLUListElement | null>(null);
let { gridProps } = useGridList(props, state, ref);
return (
<ul {...gridProps} ref={ref} className="list">
{[...state.collection].map((item) => (
<ListItem
key={item.key}
item={item}
state={state}
/>
))}
</ul>
);
}
function ListItem({ item, state }) {
let ref = React.useRef(null);
let { rowProps, gridCellProps, isPressed } =
useGridListItem(
{ node: item },
state,
ref
);
let { isFocusVisible, focusProps } = useFocusRing();
let showCheckbox =
state.selectionManager.selectionMode !== 'none' &&
state.selectionManager.selectionBehavior === 'toggle';
return (
<li
{...mergeProps(rowProps, focusProps)}
ref={ref}
className={` `}
>
<div {...gridCellProps}>
{showCheckbox && (
<ListCheckbox item={item} state={state} />
)}
{item.rendered}
</div>
</li>
);
}
import {
mergeProps,
useFocusRing,
useGridList,
useGridListItem
} from 'react-aria';
import {useListState} from 'react-stately';
import {useRef} from 'react';
function List(props) {
let state =
useListState(props);
let ref = useRef<
| HTMLUListElement
| null
>(null);
let { gridProps } =
useGridList(
props,
state,
ref
);
return (
<ul
{...gridProps}
ref={ref}
className="list"
>
{[
...state
.collection
].map((item) => (
<ListItem
key={item.key}
item={item}
state={state}
/>
))}
</ul>
);
}
function ListItem(
{ item, state }
) {
let ref = React.useRef(
null
);
let {
rowProps,
gridCellProps,
isPressed
} = useGridListItem(
{ node: item },
state,
ref
);
let {
isFocusVisible,
focusProps
} = useFocusRing();
let showCheckbox =
state
.selectionManager
.selectionMode !==
'none' &&
state
.selectionManager
.selectionBehavior ===
'toggle';
return (
<li
{...mergeProps(
rowProps,
focusProps
)}
ref={ref}
className={` `}
>
<div
{...gridCellProps}
>
{showCheckbox &&
(
<ListCheckbox
item={item}
state={state}
/>
)}
{item.rendered}
</div>
</li>
);
}
Now we can render a basic example list, with multiple selection and interactive children in each item.
import {Item} from 'react-stately';
// Reuse the Button from your component library. See below.
import {Button} from 'your-component-library';
<List
aria-label="Example List"
selectionMode="multiple"
selectionBehavior="replace"
>
<Item textValue="Charizard">
Charizard
<Button onPress={() => alert(`Info for Charizard...`)}>Info</Button>
</Item>
<Item textValue="Blastoise">
Blastoise
<Button onPress={() => alert(`Info for Blastoise...`)}>Info</Button>
</Item>
<Item textValue="Venusaur">
Venusaur
<Button onPress={() => alert(`Info for Venusaur...`)}>Info</Button>
</Item>
<Item textValue="Pikachu">
Pikachu
<Button onPress={() => alert(`Info for Pikachu...`)}>Info</Button>
</Item>
</List>
import {Item} from 'react-stately';
// Reuse the Button from your component library. See below.
import {Button} from 'your-component-library';
<List
aria-label="Example List"
selectionMode="multiple"
selectionBehavior="replace"
>
<Item textValue="Charizard">
Charizard
<Button
onPress={() => alert(`Info for Charizard...`)}
>
Info
</Button>
</Item>
<Item textValue="Blastoise">
Blastoise
<Button
onPress={() => alert(`Info for Blastoise...`)}
>
Info
</Button>
</Item>
<Item textValue="Venusaur">
Venusaur
<Button onPress={() => alert(`Info for Venusaur...`)}>
Info
</Button>
</Item>
<Item textValue="Pikachu">
Pikachu
<Button onPress={() => alert(`Info for Pikachu...`)}>
Info
</Button>
</Item>
</List>
import {Item} from 'react-stately';
// Reuse the Button from your component library. See below.
import {Button} from 'your-component-library';
<List
aria-label="Example List"
selectionMode="multiple"
selectionBehavior="replace"
>
<Item textValue="Charizard">
Charizard
<Button
onPress={() =>
alert(
`Info for Charizard...`
)}
>
Info
</Button>
</Item>
<Item textValue="Blastoise">
Blastoise
<Button
onPress={() =>
alert(
`Info for Blastoise...`
)}
>
Info
</Button>
</Item>
<Item textValue="Venusaur">
Venusaur
<Button
onPress={() =>
alert(
`Info for Venusaur...`
)}
>
Info
</Button>
</Item>
<Item textValue="Pikachu">
Pikachu
<Button
onPress={() =>
alert(
`Info for Pikachu...`
)}
>
Info
</Button>
</Item>
</List>
Show CSS
.list {
padding: 0;
list-style: none;
background: var(--page-background);
border: 1px solid var(--spectrum-global-color-gray-400);
max-width: 400px;
min-width: 200px;
max-height: 250px;
overflow: auto;
}
.list li {
padding: 8px;
outline: none;
cursor: default;
}
.list li:nth-child(2n) {
background: var(--spectrum-alias-highlight-hover);
}
.list li.pressed {
background: var(--spectrum-global-color-gray-200);
}
.list li[aria-selected=true] {
background: slateblue;
color: white;
}
.list li.focus-visible {
outline: 2px solid slateblue;
outline-offset: -3px;
}
.list li.focus-visible[aria-selected=true] {
outline-color: white;
}
.list li[aria-disabled] {
opacity: 0.4;
}
.list [role=gridcell] {
display: flex;
align-items: center;
gap: 4px;
}
.list li button {
margin-left: auto;
}
/* iOS Safari has a bug that prevents accent-color: white from working. */
@supports not (-webkit-touch-callout: none) {
.list li input[type=checkbox] {
accent-color: white;
}
}
.list {
padding: 0;
list-style: none;
background: var(--page-background);
border: 1px solid var(--spectrum-global-color-gray-400);
max-width: 400px;
min-width: 200px;
max-height: 250px;
overflow: auto;
}
.list li {
padding: 8px;
outline: none;
cursor: default;
}
.list li:nth-child(2n) {
background: var(--spectrum-alias-highlight-hover);
}
.list li.pressed {
background: var(--spectrum-global-color-gray-200);
}
.list li[aria-selected=true] {
background: slateblue;
color: white;
}
.list li.focus-visible {
outline: 2px solid slateblue;
outline-offset: -3px;
}
.list li.focus-visible[aria-selected=true] {
outline-color: white;
}
.list li[aria-disabled] {
opacity: 0.4;
}
.list [role=gridcell] {
display: flex;
align-items: center;
gap: 4px;
}
.list li button {
margin-left: auto;
}
/* iOS Safari has a bug that prevents accent-color: white from working. */
@supports not (-webkit-touch-callout: none) {
.list li input[type=checkbox] {
accent-color: white;
}
}
.list {
padding: 0;
list-style: none;
background: var(--page-background);
border: 1px solid var(--spectrum-global-color-gray-400);
max-width: 400px;
min-width: 200px;
max-height: 250px;
overflow: auto;
}
.list li {
padding: 8px;
outline: none;
cursor: default;
}
.list li:nth-child(2n) {
background: var(--spectrum-alias-highlight-hover);
}
.list li.pressed {
background: var(--spectrum-global-color-gray-200);
}
.list li[aria-selected=true] {
background: slateblue;
color: white;
}
.list li.focus-visible {
outline: 2px solid slateblue;
outline-offset: -3px;
}
.list li.focus-visible[aria-selected=true] {
outline-color: white;
}
.list li[aria-disabled] {
opacity: 0.4;
}
.list [role=gridcell] {
display: flex;
align-items: center;
gap: 4px;
}
.list li button {
margin-left: auto;
}
/* iOS Safari has a bug that prevents accent-color: white from working. */
@supports not (-webkit-touch-callout: none) {
.list li input[type=checkbox] {
accent-color: white;
}
}
Adding selection checkboxes#
Next, let's add support for selection checkboxes to allow the user to select items explicitly.
This is done using the useGridListSelectionCheckbox
hook. It is passed the key
of the item it is contained within. When the user
checks or unchecks the checkbox, the row will be added or removed from the List's selection.
The Checkbox
component used in this example is independent and can be used separately from useGridList
. The code is available below.
import {useGridListSelectionCheckbox} from 'react-aria';
// Reuse the Checkbox from your component library. See below for details.
import {Checkbox} from 'your-component-library';
function ListCheckbox({ item, state }) {
let { checkboxProps } = useGridListSelectionCheckbox(
{ key: item.key },
state
);
return <Checkbox {...checkboxProps} />;
}
import {useGridListSelectionCheckbox} from 'react-aria';
// Reuse the Checkbox from your component library. See below for details.
import {Checkbox} from 'your-component-library';
function ListCheckbox({ item, state }) {
let { checkboxProps } = useGridListSelectionCheckbox({
key: item.key
}, state);
return <Checkbox {...checkboxProps} />;
}
import {useGridListSelectionCheckbox} from 'react-aria';
// Reuse the Checkbox from your component library. See below for details.
import {Checkbox} from 'your-component-library';
function ListCheckbox(
{ item, state }
) {
let { checkboxProps } =
useGridListSelectionCheckbox(
{ key: item.key },
state
);
return (
<Checkbox
{...checkboxProps}
/>
);
}
The following example shows an example list with multiple selection using checkboxes and the default toggle
selection behavior.
<List aria-label="List with selection" selectionMode="multiple">
<Item textValue="Charizard">
Charizard
<Button onPress={() => alert(`Info for Charizard...`)}>Info</Button>
</Item>
<Item textValue="Blastoise">
Blastoise
<Button onPress={() => alert(`Info for Blastoise...`)}>Info</Button>
</Item>
<Item textValue="Venusaur">
Venusaur
<Button onPress={() => alert(`Info for Venusaur...`)}>Info</Button>
</Item>
<Item textValue="Pikachu">
Pikachu
<Button onPress={() => alert(`Info for Pikachu...`)}>Info</Button>
</Item>
</List>
<List
aria-label="List with selection"
selectionMode="multiple"
>
<Item textValue="Charizard">
Charizard
<Button
onPress={() => alert(`Info for Charizard...`)}
>
Info
</Button>
</Item>
<Item textValue="Blastoise">
Blastoise
<Button
onPress={() => alert(`Info for Blastoise...`)}
>
Info
</Button>
</Item>
<Item textValue="Venusaur">
Venusaur
<Button onPress={() => alert(`Info for Venusaur...`)}>
Info
</Button>
</Item>
<Item textValue="Pikachu">
Pikachu
<Button onPress={() => alert(`Info for Pikachu...`)}>
Info
</Button>
</Item>
</List>
<List
aria-label="List with selection"
selectionMode="multiple"
>
<Item textValue="Charizard">
Charizard
<Button
onPress={() =>
alert(
`Info for Charizard...`
)}
>
Info
</Button>
</Item>
<Item textValue="Blastoise">
Blastoise
<Button
onPress={() =>
alert(
`Info for Blastoise...`
)}
>
Info
</Button>
</Item>
<Item textValue="Venusaur">
Venusaur
<Button
onPress={() =>
alert(
`Info for Venusaur...`
)}
>
Info
</Button>
</Item>
<Item textValue="Pikachu">
Pikachu
<Button
onPress={() =>
alert(
`Info for Pikachu...`
)}
>
Info
</Button>
</Item>
</List>
And that's it! We now have a fully interactive List component that can support keyboard navigation, single or multiple selection. In addition, it is fully accessible for screen readers and other assistive technology. See below for more examples of how to use the List component that we've built.
Checkbox#
The Checkbox
component is used in the above example for row selection. It is built using the useCheckbox hook, and can be shared with many other components.
Show code
import {useToggleState} from 'react-stately';
import {useCheckbox} from 'react-aria';
function Checkbox(props) {
let inputRef = useRef(null);
let { inputProps } = useCheckbox(
props,
useToggleState(props),
inputRef
);
return <input {...inputProps} ref={inputRef} />;
}
import {useToggleState} from 'react-stately';
import {useCheckbox} from 'react-aria';
function Checkbox(props) {
let inputRef = useRef(null);
let { inputProps } = useCheckbox(
props,
useToggleState(props),
inputRef
);
return <input {...inputProps} ref={inputRef} />;
}
import {useToggleState} from 'react-stately';
import {useCheckbox} from 'react-aria';
function Checkbox(
props
) {
let inputRef = useRef(
null
);
let { inputProps } =
useCheckbox(
props,
useToggleState(
props
),
inputRef
);
return (
<input
{...inputProps}
ref={inputRef}
/>
);
}
Button#
The Button
component is used in the above example to show how rows can contain interactive elements. It is built using the useButton hook, and can be shared with many other components.
Show code
import {useButton} from 'react-aria';
function Button(props) {
let ref = React.useRef(null);
let { buttonProps } = useButton(props, ref);
return <button {...buttonProps} ref={ref}>{props.children}</button>;
}
import {useButton} from 'react-aria';
function Button(props) {
let ref = React.useRef(null);
let { buttonProps } = useButton(props, ref);
return (
<button {...buttonProps} ref={ref}>
{props.children}
</button>
);
}
import {useButton} from 'react-aria';
function Button(props) {
let ref = React.useRef(
null
);
let { buttonProps } =
useButton(
props,
ref
);
return (
<button
{...buttonProps}
ref={ref}
>
{props.children}
</button>
);
}
Usage#
Dynamic collections#
So far, our examples have shown static collections, where the data is hard coded. Dynamic collections, as shown below, can be used when the data comes from an external data source such as an API, or updates over time. In the example below, the rows are provided to the List via a render function.
function ExampleList(props) {
let rows = [
{ id: 1, name: 'Games' },
{ id: 2, name: 'Program Files' },
{ id: 3, name: 'bootmgr' },
{ id: 4, name: 'log.txt' }
];
return (
<List
aria-label="Example dynamic collection List"
selectionMode="multiple"
items={rows}
{...props}
>
{(item) => (
<Item textValue={item.name}>
{item.name}
<Button onPress={() => alert(`Info for ...`)}>
Info
</Button>
</Item>
)}
</List>
);
}
function ExampleList(props) {
let rows = [
{ id: 1, name: 'Games' },
{ id: 2, name: 'Program Files' },
{ id: 3, name: 'bootmgr' },
{ id: 4, name: 'log.txt' }
];
return (
<List
aria-label="Example dynamic collection List"
selectionMode="multiple"
items={rows}
{...props}
>
{(item) => (
<Item textValue={item.name}>
{item.name}
<Button
onPress={() =>
alert(`Info for ...`)}
>
Info
</Button>
</Item>
)}
</List>
);
}
function ExampleList(
props
) {
let rows = [
{
id: 1,
name: 'Games'
},
{
id: 2,
name:
'Program Files'
},
{
id: 3,
name: 'bootmgr'
},
{
id: 4,
name: 'log.txt'
}
];
return (
<List
aria-label="Example dynamic collection List"
selectionMode="multiple"
items={rows}
{...props}
>
{(item) => (
<Item
textValue={item
.name}
>
{item.name}
<Button
onPress={() =>
alert(
`Info for
...`)}
>
Info
</Button>
</Item>
)}
</List>
);
}
Single selection#
By default, useListState
doesn't allow row selection but this can be enabled using the selectionMode
prop. Use defaultSelectedKeys
to provide a default set of selected rows.
Note that the value of the selected keys must match the key
prop of the row.
The example below enables single selection mode, and uses defaultSelectedKeys
to select the row with key equal to "2".
A user can click on a different row to change the selection, or click on the same row again to deselect it entirely.
// Using the example above
<ExampleList
aria-label="List with single selection"
selectionMode="single"
selectionBehavior="replace"
defaultSelectedKeys={[2]}
/>
// Using the example above
<ExampleList
aria-label="List with single selection"
selectionMode="single"
selectionBehavior="replace"
defaultSelectedKeys={[2]}
/>
// Using the example above
<ExampleList
aria-label="List with single selection"
selectionMode="single"
selectionBehavior="replace"
defaultSelectedKeys={[
2
]}
/>
Multiple selection#
Multiple selection can be enabled by setting selectionMode
to multiple
.
<ExampleList
aria-label="List with multiple selection"
selectionMode="multiple"
defaultSelectedKeys={[2, 4]}
/>
<ExampleList
aria-label="List with multiple selection"
selectionMode="multiple"
defaultSelectedKeys={[2, 4]}
/>
<ExampleList
aria-label="List with multiple selection"
selectionMode="multiple"
defaultSelectedKeys={[
2,
4
]}
/>
Disallow empty selection#
useGridList
also supports a disallowEmptySelection
prop which forces the user to have at least one row in the List selected at all times.
In this mode, if a single row is selected and the user presses it, it will not be deselected.
<ExampleList
aria-label="List with disallowed empty selection"
selectionMode="multiple"
defaultSelectedKeys={[2]}
disallowEmptySelection
/>
<ExampleList
aria-label="List with disallowed empty selection"
selectionMode="multiple"
defaultSelectedKeys={[2]}
disallowEmptySelection
/>
<ExampleList
aria-label="List with disallowed empty selection"
selectionMode="multiple"
defaultSelectedKeys={[
2
]}
disallowEmptySelection
/>
Controlled selection#
To programmatically control row selection, use the selectedKeys
prop paired with the onSelectionChange
callback. The key
prop from the selected rows will
be passed into the callback when the row is pressed, allowing you to update state accordingly.
function PokemonList(props) {
let rows = [
{ id: 1, name: 'Charizard' },
{ id: 2, name: 'Blastoise' },
{ id: 3, name: 'Venusaur' },
{ id: 4, name: 'Pikachu' }
];
let [selectedKeys, setSelectedKeys] = React.useState(new Set([2]));
return (
<List
aria-label="List with controlled selection"
items={rows}
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
{...props}
>
{(item) => <Item>{item.name}</Item>}
</List>
);
}
function PokemonList(props) {
let rows = [
{ id: 1, name: 'Charizard' },
{ id: 2, name: 'Blastoise' },
{ id: 3, name: 'Venusaur' },
{ id: 4, name: 'Pikachu' }
];
let [selectedKeys, setSelectedKeys] = React.useState(
new Set([2])
);
return (
<List
aria-label="List with controlled selection"
items={rows}
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
{...props}
>
{(item) => <Item>{item.name}</Item>}
</List>
);
}
function PokemonList(
props
) {
let rows = [
{
id: 1,
name: 'Charizard'
},
{
id: 2,
name: 'Blastoise'
},
{
id: 3,
name: 'Venusaur'
},
{
id: 4,
name: 'Pikachu'
}
];
let [
selectedKeys,
setSelectedKeys
] = React.useState(
new Set([2])
);
return (
<List
aria-label="List with controlled selection"
items={rows}
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
{...props}
>
{(item) => (
<Item>
{item.name}
</Item>
)}
</List>
);
}
Disabled rows#
You can disable specific rows by providing an array of keys to useListState
via the disabledKeys
prop. This will disable all interactions on disabled rows,
unless the disabledBehavior
prop is used to change this behavior.
Note that you are responsible for the styling of disabled rows, however, the selection checkbox will be automatically disabled.
// Using the example above
<PokemonList
aria-label="List with disabled rows"
selectionMode="multiple"
disabledKeys={[3]}
/>
// Using the example above
<PokemonList
aria-label="List with disabled rows"
selectionMode="multiple"
disabledKeys={[3]}
/>
// Using the example above
<PokemonList
aria-label="List with disabled rows"
selectionMode="multiple"
disabledKeys={[3]}
/>
When disabledBehavior
is set to selection
, interactions such as focus, dragging, or actions can still be performed on disabled rows.
<PokemonList
aria-label="List with selection disabled for disabled rows"
selectionMode="multiple"
disabledKeys={[3]}
disabledBehavior="selection"
/>
<PokemonList
aria-label="List with selection disabled for disabled rows"
selectionMode="multiple"
disabledKeys={[3]}
disabledBehavior="selection"
/>
<PokemonList
aria-label="List with selection disabled for disabled rows"
selectionMode="multiple"
disabledKeys={[3]}
disabledBehavior="selection"
/>
Selection behavior#
By default, useGridList
uses the "toggle"
selection behavior, which behaves like a checkbox group: clicking, tapping, or pressing the Space or Enter keys toggles selection for the focused row. Using the arrow keys moves focus but does not change selection. The "toggle"
selection mode is often paired with a checkbox in each row as an explicit affordance for selection.
When selectionBehavior
is set to "replace"
, clicking a row with the mouse replaces the selection with only that row. Using the arrow keys moves both focus and selection. To select multiple rows, modifier keys such as Ctrl, Cmd, and Shift can be used. On touch screen devices, selection always behaves as toggle since modifier keys may not be available.
These selection styles implement the behaviors defined in Aria Practices.
<PokemonList
aria-label="List with replace selection behavior"
selectionMode="multiple"
selectionBehavior="replace"
/>
<PokemonList
aria-label="List with replace selection behavior"
selectionMode="multiple"
selectionBehavior="replace"
/>
<PokemonList
aria-label="List with replace selection behavior"
selectionMode="multiple"
selectionBehavior="replace"
/>
Row actions#
useGridList
supports row actions via the onAction
prop, which is useful for functionality such as navigation. When nothing is selected, the list performs actions by default when clicking or tapping a row.
Items may be selected using the checkbox, or by long pressing on touch devices. When at least one item is selected, the list is in selection mode, and clicking or tapping a row toggles the selection. Actions may also
be triggered via the Enter key, and selection using the Space key.
This behavior is slightly different when selectionBehavior="replace"
, where single clicking selects the row and actions are performed via double click. Touch and keyboard behaviors are unaffected.
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24 }}>
<ExampleList
aria-label="Checkbox selection list with row actions"
selectionMode="multiple"
selectionBehavior="toggle"
onAction={(key) => alert(`Opening item ...`)}
/>
<ExampleList
aria-label="Highlight selection list with row actions"
selectionMode="multiple"
selectionBehavior="replace"
onAction={(key) => alert(`Opening item ...`)}
/>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 24 }}>
<ExampleList
aria-label="Checkbox selection list with row actions"
selectionMode="multiple"
selectionBehavior="toggle"
onAction={(key) => alert(`Opening item ...`)}
/>
<ExampleList
aria-label="Highlight selection list with row actions"
selectionMode="multiple"
selectionBehavior="replace"
onAction={(key) => alert(`Opening item ...`)}
/>
</div>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: 24
}}
>
<ExampleList
aria-label="Checkbox selection list with row actions"
selectionMode="multiple"
selectionBehavior="toggle"
onAction={(key) =>
alert(
`Opening item
...`)}
/>
<ExampleList
aria-label="Highlight selection list with row actions"
selectionMode="multiple"
selectionBehavior="replace"
onAction={(key) =>
alert(
`Opening item
...`)}
/>
</div>
Links#
Items in a GridList may also be links to another page or website. This can be achieved by passing the href
prop to the <Item>
component. Links behave the same way as described above for row actions depending on the selectionMode
and selectionBehavior
.
<List aria-label="Links" selectionMode="multiple">
<Item href="https://adobe.com/" target="_blank">Adobe</Item>
<Item href="https://apple.com/" target="_blank">Apple</Item>
<Item href="https://google.com/" target="_blank">Google</Item>
<Item href="https://microsoft.com/" target="_blank">Microsoft</Item>
</List>
<List aria-label="Links" selectionMode="multiple">
<Item href="https://adobe.com/" target="_blank">
Adobe
</Item>
<Item href="https://apple.com/" target="_blank">
Apple
</Item>
<Item href="https://google.com/" target="_blank">
Google
</Item>
<Item href="https://microsoft.com/" target="_blank">
Microsoft
</Item>
</List>
<List
aria-label="Links"
selectionMode="multiple"
>
<Item
href="https://adobe.com/"
target="_blank"
>
Adobe
</Item>
<Item
href="https://apple.com/"
target="_blank"
>
Apple
</Item>
<Item
href="https://google.com/"
target="_blank"
>
Google
</Item>
<Item
href="https://microsoft.com/"
target="_blank"
>
Microsoft
</Item>
</List>
Client side routing#
The <Item>
component works with frameworks and client side routers like Next.js and React Router. As with other React Aria components that support links, this works via the RouterProvider
component at the root of your app. See the client side routing guide to learn how to set this up.
Asynchronous loading#
This example uses the useAsyncList hook to handle asynchronous loading of data from a server. You may additionally want to display a spinner to indicate the loading state to the user, or support features like infinite scroll to load more data.
import {useAsyncList} from 'react-stately';
function AsyncList() {
let list = useAsyncList({
async load({ signal, cursor }) {
if (cursor) {
cursor = cursor.replace(/^http:\/\//i, 'https://');
}
let res = await fetch(
cursor || `https://swapi.py4e.com/api/people/?search=`,
{ signal }
);
let json = await res.json();
return {
items: json.results,
cursor: json.next
};
}
});
return (
<List
selectionMode="multiple"
aria-label="Async loading ListView example"
items={list.items}
>
{(item) => <Item key={item.name}>{item.name}</Item>}
</List>
);
}
import {useAsyncList} from 'react-stately';
function AsyncList() {
let list = useAsyncList({
async load({ signal, cursor }) {
if (cursor) {
cursor = cursor.replace(/^http:\/\//i, 'https://');
}
let res = await fetch(
cursor ||
`https://swapi.py4e.com/api/people/?search=`,
{ signal }
);
let json = await res.json();
return {
items: json.results,
cursor: json.next
};
}
});
return (
<List
selectionMode="multiple"
aria-label="Async loading ListView example"
items={list.items}
>
{(item) => <Item key={item.name}>{item.name}</Item>}
</List>
);
}
import {useAsyncList} from 'react-stately';
function AsyncList() {
let list =
useAsyncList({
async load(
{
signal,
cursor
}
) {
if (cursor) {
cursor = cursor
.replace(
/^http:\/\//i,
'https://'
);
}
let res =
await fetch(
cursor ||
`https://swapi.py4e.com/api/people/?search=`,
{ signal }
);
let json =
await res
.json();
return {
items:
json.results,
cursor:
json.next
};
}
});
return (
<List
selectionMode="multiple"
aria-label="Async loading ListView example"
items={list.items}
>
{(item) => (
<Item
key={item.name}
>
{item.name}
</Item>
)}
</List>
);
}
Internationalization#
useGridList
handles some aspects of internationalization automatically.
For example, type to select is implemented with an
Intl.Collator
for internationalized string matching, and keyboard navigation is mirrored in right-to-left languages.
You are responsible for localizing all text content within the List.
RTL#
In right-to-left languages, the list layout should be mirrored. The row contents should be ordered from right to left and the row's text alignment should be inverted. Ensure that your CSS accounts for this.