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>