Selection
Many collection components support selecting items by clicking or tapping them, or by using the keyboard. This page discusses how to handle selection events, how to control selection programmatically, and the data structures used to represent a selection.
Multiple selection#
Selection is handled by the onSelectionChange
event, which is supported on most collection components. Controlled
behavior is supported by the selectedKeys
prop, and uncontrolled behavior is supported by the defaultSelectedKeys
prop. These props are passed to the top-level collection component, and accept a set of unique item ids.
This allows marking items as selected by their id even before they are loaded, which can be useful when you know
what items should be selected on initial render, before data loading has completed.
Selection is represented by a Set
object. You can also pass any iterable collection (e.g. an array) to the selectedKeys
and defaultSelectedKeys
props,
but the onSelectionChange
event will always pass back a Set.
Selection is supported on both static and dynamic collections. The following example shows how to implement controlled selection behavior on a static collection, but could be applied to a dynamic collection the same way.
let [selectedKeys, setSelectedKeys] = useState(new Set());
<ListBox selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys}>
<ListBoxItem id="one">One</ListBoxItem>
<ListBoxItem id="two">Two</ListBoxItem>
<ListBoxItem id="three">Three</ListBoxItem>
</ListBox>
let [selectedKeys, setSelectedKeys] = useState(new Set());
<ListBox
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<ListBoxItem id="one">One</ListBoxItem>
<ListBoxItem id="two">Two</ListBoxItem>
<ListBoxItem id="three">Three</ListBoxItem>
</ListBox>
let [
selectedKeys,
setSelectedKeys
] = useState(new Set());
<ListBox
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<ListBoxItem id="one">
One
</ListBoxItem>
<ListBoxItem id="two">
Two
</ListBoxItem>
<ListBoxItem id="three">
Three
</ListBoxItem>
</ListBox>
When the user selects an item, its id
is added to the selectedKeys
set via the onSelectionChange
event.
Single selection#
So far, we've discussed multiple selection. However, you may wish to limit selection to a single item instead.
In some components, like a select or combo box, only single selection is supported. In this case, the singular
selectedKey
and defaultSelectedKey
props are available instead of their plural variants. These accept a
single id instead of a Set
as their value, and onSelectionChange
is also called with a single id.
let [selectedKey, setSelectedKey] = useState(null);
<MyComboBox
selectedKey={selectedKey} onSelectionChange={setSelectedKey}>
<ListBoxItem id="one">One</ListBoxItem>
<ListBoxItem id="two">Two</ListBoxItem>
<ListBoxItem id="three">Three</ListBoxItem>
</MyComboBox>
let [selectedKey, setSelectedKey] = useState(null);
<MyComboBox
selectedKey={selectedKey} onSelectionChange={setSelectedKey}>
<ListBoxItem id="one">One</ListBoxItem>
<ListBoxItem id="two">Two</ListBoxItem>
<ListBoxItem id="three">Three</ListBoxItem>
</MyComboBox>
let [
selectedKey,
setSelectedKey
] = useState(null);
<MyComboBox
selectedKey={selectedKey} onSelectionChange={setSelectedKey}
>
<ListBoxItem id="one">
One
</ListBoxItem>
<ListBoxItem id="two">
Two
</ListBoxItem>
<ListBoxItem id="three">
Three
</ListBoxItem>
</MyComboBox>
In components which support multiple selection, you can limit the selection to a single item using the
selectionMode
prop. This continues to accept selectedKeys
and defaultSelectedKeys
as a Set
, but it will
only contain a single id at a time.
let [selectedKeys, setSelectedKeys] = useState(new Set());
<ListBox
selectionMode="single" selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}>
<ListBoxItem id="one">One</ListBoxItem>
<ListBoxItem id="two">Two</ListBoxItem>
<ListBoxItem id="three">Three</ListBoxItem>
</ListBox>
let [selectedKeys, setSelectedKeys] = useState(new Set());
<ListBox
selectionMode="single" selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}>
<ListBoxItem id="one">One</ListBoxItem>
<ListBoxItem id="two">Two</ListBoxItem>
<ListBoxItem id="three">Three</ListBoxItem>
</ListBox>
let [
selectedKeys,
setSelectedKeys
] = useState(new Set());
<ListBox
selectionMode="single" selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<ListBoxItem id="one">
One
</ListBoxItem>
<ListBoxItem id="two">
Two
</ListBoxItem>
<ListBoxItem id="three">
Three
</ListBoxItem>
</ListBox>
Dynamic data#
When data in a collection changes, the selection state may need to be updated accordingly. For example, if a selected item is
deleted, it should be removed from the set of selected keys. You can do this yourself, or use the useListData
and useTreeData
hooks to handle this automatically.
import {useListData} from 'react-stately';
let list = useListData({
initialItems: [
{name: 'Aardvark'},
{name: 'Kangaroo'},
{name: 'Snake'}
],
initialSelectedKeys: ['Kangaroo'],
getKey: item => item.name
});
function removeItem() {
// Removing the list item will also remove it from the selection state.
list.remove('Kangaroo');}
<ListBox
items={list.items}
selectedKeys={list.selectedKeys}
onSelectionChange={list.setSelectedKeys}>
{item => <ListBoxItem id={item.name}>{item.name}</ListBoxItem>}
</ListBox>
import {useListData} from 'react-stately';
let list = useListData({
initialItems: [
{ name: 'Aardvark' },
{ name: 'Kangaroo' },
{ name: 'Snake' }
],
initialSelectedKeys: ['Kangaroo'],
getKey: (item) => item.name
});
function removeItem() {
// Removing the list item will also remove it from the selection state.
list.remove('Kangaroo');}
<ListBox
items={list.items}
selectedKeys={list.selectedKeys}
onSelectionChange={list.setSelectedKeys}
>
{(item) => (
<ListBoxItem id={item.name}>{item.name}</ListBoxItem>
)}
</ListBox>
import {useListData} from 'react-stately';
let list = useListData({
initialItems: [
{ name: 'Aardvark' },
{ name: 'Kangaroo' },
{ name: 'Snake' }
],
initialSelectedKeys: [
'Kangaroo'
],
getKey: (item) =>
item.name
});
function removeItem() {
// Removing the list item will also remove it from the selection state.
list.remove(
'Kangaroo'
);}
<ListBox
items={list.items}
selectedKeys={list
.selectedKeys}
onSelectionChange={list
.setSelectedKeys}
>
{(item) => (
<ListBoxItem
id={item.name}
>
{item.name}
</ListBoxItem>
)}
</ListBox>
For more information, see useListData and useTreeData.
Select All#
Some components support a checkbox to select all items in the collection, or a keyboard shortcut like ⌘ Cmd + A. This represents a selection of all items in the collection, regardless of whether or not all items have been loaded from the network. For example, when using a component with infinite scrolling support, the user will be unaware that all items are not yet loaded because it loads more transparently to them as they scroll down. For this reason, it makes sense for select all to represent all items, not just the loaded ones.
When a select all event occurs, onSelectionChange
is called with the string "all"
rather than a set of selected keys. selectedKeys
and defaultSelectedKeys
can also be set to "all"
to programmatically select all items. This represents all items in the collection,
whether currently loaded or not. The application must adjust its handling of bulk actions in this case to apply to the entire collection
rather than only the keys available to it locally.
let [selectedKeys, setSelectedKeys] = useState(new Set());
function performBulkAction() {
if (selectedKeys === 'all') { // perform action on all items
} else {
// perform action on selected items in selectedKeys
}
}
<ListBox
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}>
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</ListBox>
let [selectedKeys, setSelectedKeys] = useState(new Set());
function performBulkAction() {
if (selectedKeys === 'all') { // perform action on all items
} else {
// perform action on selected items in selectedKeys
}
}
<ListBox
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}>
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</ListBox>
let [
selectedKeys,
setSelectedKeys
] = useState(new Set());
function performBulkAction() {
if (
selectedKeys ===
'all'
) {// perform action on all items
} else {
// perform action on selected items in selectedKeys
}
}
<ListBox
items={items}
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
{(item) => (
<ListBoxItem>
{item.name}
</ListBoxItem>
)}
</ListBox>
Hooks#
If you're using React Aria and React Stately hooks rather than components, the collection and selection APIs are slightly different. See the React Stately selection documentation for more details.