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 keys that are selected.
This allows marking items as selected by their key 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}>
<Item key="one">One</Item>
<Item key="two">Two</Item>
<Item key="three">Three</Item>
</ListBox>
let [selectedKeys, setSelectedKeys] = useState(new Set());
<ListBox
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<Item key="one">One</Item>
<Item key="two">Two</Item>
<Item key="three">Three</Item>
</ListBox>
let [
selectedKeys,
setSelectedKeys
] = useState(new Set());
<ListBox
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<Item key="one">
One
</Item>
<Item key="two">
Two
</Item>
<Item key="three">
Three
</Item>
</ListBox>
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 key instead of a Set as their value, and onSelectionChange
is also called with a single key.
let [selectedKey, setSelectedKey] = useState(null);
<ComboBox selectedKey={selectedKey} onSelectionChange={setSelectedKey}>
<Item key="one">One</Item>
<Item key="two">Two</Item>
<Item key="three">Three</Item>
</ComboBox>
let [selectedKey, setSelectedKey] = useState(null);
<ComboBox
selectedKey={selectedKey}
onSelectionChange={setSelectedKey}
>
<Item key="one">One</Item>
<Item key="two">Two</Item>
<Item key="three">Three</Item>
</ComboBox>
let [
selectedKey,
setSelectedKey
] = useState(null);
<ComboBox
selectedKey={selectedKey}
onSelectionChange={setSelectedKey}
>
<Item key="one">
One
</Item>
<Item key="two">
Two
</Item>
<Item key="three">
Three
</Item>
</ComboBox>
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 key at a time.
let [selectedKeys, setSelectedKeys] = useState(new Set());
<ListBox
selectionMode="single"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}>
<Item key="one">One</Item>
<Item key="two">Two</Item>
<Item key="three">Three</Item>
</ListBox>
let [selectedKeys, setSelectedKeys] = useState(new Set());
<ListBox
selectionMode="single"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}>
<Item key="one">One</Item>
<Item key="two">Two</Item>
<Item key="three">Three</Item>
</ListBox>
let [
selectedKeys,
setSelectedKeys
] = useState(new Set());
<ListBox
selectionMode="single"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
>
<Item key="one">
One
</Item>
<Item key="two">
Two
</Item>
<Item key="three">
Three
</Item>
</ListBox>
Selected key data type#
As mentioned in the collection docs, there are two ways that keys can be defined for your collection items.
The method you use will affect the data type you provide to your defaultSelectedKey(s)
and selectedKey(s)
props and what onSelectionChange
returns.
If you directly specify the key on each Item
using the key
prop, defaultSelectedKey(s)
/selectedKey(s)
will expect a string/list of strings regardless of the
original data type provided to the Item
's key
prop. As seen in the example below, the selectedKey
is initialized to "2"
rather than 2
despite the fact that
the dataIds
provided to each Item
key
are numbers.
let items = [
{ name: 'Aardvark', dataId: 1 },
{ name: 'Kangaroo', dataId: 2 },
{ name: 'Snake', dataId: 3 }
];
let [selectedKey, setSelectedKey] = useState('2');
<ComboBox selectedKey={selectedKey} onSelectionChange={setSelectedKey}>
{(item) => <Item key={item.dataId}>{item.name}</Item>}
</ComboBox>
let items = [
{ name: 'Aardvark', dataId: 1 },
{ name: 'Kangaroo', dataId: 2 },
{ name: 'Snake', dataId: 3 }
];
let [selectedKey, setSelectedKey] = useState('2');
<ComboBox
selectedKey={selectedKey}
onSelectionChange={setSelectedKey}
>
{(item) => <Item key={item.dataId}>{item.name}</Item>}
</ComboBox>
let items = [
{
name: 'Aardvark',
dataId: 1
},
{
name: 'Kangaroo',
dataId: 2
},
{
name: 'Snake',
dataId: 3
}
];
let [
selectedKey,
setSelectedKey
] = useState('2');
<ComboBox
selectedKey={selectedKey}
onSelectionChange={setSelectedKey}
>
{(item) => (
<Item
key={item.dataId}
>
{item.name}
</Item>
)}
</ComboBox>
If you specify a key
or id
within your items
array and forgo specifying a key on the Item
instead, defaultSelectedKey(s)
/selectedKey(s)
should match the data type
of the key
or id
. As seen in the example below, the selectedKey
is initialized as a number
, matching the key
type of the items
array.
let items = [
{ name: 'Aardvark', key: 1 },
{ name: 'Kangaroo', key: 2 },
{ name: 'Snake', key: 3 }
];
let [selectedKey, setSelectedKey] = useState(2);
<ComboBox selectedKey={selectedKey} onSelectionChange={setSelectedKey}>
{(item) => <Item>{item.name}</Item>}
</ComboBox>
let items = [
{ name: 'Aardvark', key: 1 },
{ name: 'Kangaroo', key: 2 },
{ name: 'Snake', key: 3 }
];
let [selectedKey, setSelectedKey] = useState(2);
<ComboBox
selectedKey={selectedKey}
onSelectionChange={setSelectedKey}
>
{(item) => <Item>{item.name}</Item>}
</ComboBox>
let items = [
{
name: 'Aardvark',
key: 1
},
{
name: 'Kangaroo',
key: 2
},
{
name: 'Snake',
key: 3
}
];
let [
selectedKey,
setSelectedKey
] = useState(2);
<ComboBox
selectedKey={selectedKey}
onSelectionChange={setSelectedKey}
>
{(item) => (
<Item>
{item.name}
</Item>
)}
</ComboBox>
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, but the useListData
and useTreeData
hooks can handle this automatically.
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 => <Item key={item.name}>{item.name}</Item>}
</ListBox>
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) => <Item key={item.name}>{item.name}</Item>}
</ListBox>
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) => (
<Item
key={item.name}
>
{item.name}
</Item>
)}
</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 => <Item>{item.name}</Item>}
</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 => <Item>{item.name}</Item>}
</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) => (
<Item>
{item.name}
</Item>
)}
</ListBox>