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
Most collection components support item selection, which is handled by the onSelectionChange event. Use the selectedKeys prop to control the selected items programmatically, or defaultSelectedKeys for uncontrolled behavior.
Selection is represented by a Set containing the id of each selected item. 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.
selectedKeys: lion
import {CardView, AssetCard, CardPreview, Image, Content, Text, type Selection} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useState} from 'react';
function Example() {
let [selectedKeys, setSelectedKeys] = useState<Selection>(new Set(['lion']));
return (
<div>
<CardView
aria-label="Nature photos"
selectionMode="multiple"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
styles={style({width: 360, height: 520})}
>
<AssetCard id="desert" textValue="Desert Sunset">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1705034598432-1694e203cdf3?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={400} />
</CardPreview>
<Content>
<Text slot="title">Desert Sunset</Text>
<Text slot="description">PNG • 2/3/2024</Text>
</Content>
</AssetCard>
<AssetCard id="hiking" textValue="Hiking Trail">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1722233987129-61dc344db8b6?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={900} />
</CardPreview>
<Content>
<Text slot="title">Hiking Trail</Text>
<Text slot="description">JPEG • 1/10/2022</Text>
</Content>
</AssetCard>
<AssetCard id="lion" textValue="Lion">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1629812456605-4a044aa38fbc?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={899} />
</CardPreview>
<Content>
<Text slot="title">Lion</Text>
<Text slot="description">JPEG • 8/28/2021</Text>
</Content>
</AssetCard>
<AssetCard id="mountain" textValue="Mountain Sunrise">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1722172118908-1a97c312ce8c?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={900} />
</CardPreview>
<Content>
<Text slot="title">Mountain Sunrise</Text>
<Text slot="description">PNG • 3/15/2015</Text>
</Content>
</AssetCard>
</CardView>
<p className={style({font: 'body'})}>selectedKeys: {selectedKeys === 'all' ? 'all' : [...selectedKeys].join(', ')}</p>
</div>
);
}
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. 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. 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.
selectedKeys:
import {TableView, TableHeader, Column, TableBody, Row, Cell} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useState} from 'react';
function Example() {
let [selectedKeys, setSelectedKeys] = useState(new Set());
function performBulkAction() {
if (selectedKeys === 'all') {
// perform action on all items
} else {
// perform action on selected items in selectedKeys
}
}
}
Single selection
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.
ComboBox, SegmentedControl, and Tabs only support single selection and use the selectedKey prop (singular) rather than selectedKeys.
selectedKeys: lion
import {CardView, AssetCard, CardPreview, Image, Content, Text, type Selection} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useState} from 'react';
function Example() {
let [selectedKeys, setSelectedKeys] = useState<Selection>(new Set(['lion']));
return (
<div>
<CardView
aria-label="Nature photos"
selectionMode="single"
selectedKeys={selectedKeys}
onSelectionChange={setSelectedKeys}
styles={style({width: 360, height: 320})}
>
<AssetCard id="desert" textValue="Desert Sunset">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1705034598432-1694e203cdf3?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={400} />
</CardPreview>
<Content>
<Text slot="title">Desert Sunset</Text>
<Text slot="description">PNG • 2/3/2024</Text>
</Content>
</AssetCard>
<AssetCard id="hiking" textValue="Hiking Trail">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1722233987129-61dc344db8b6?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={900} />
</CardPreview>
<Content>
<Text slot="title">Hiking Trail</Text>
<Text slot="description">JPEG • 1/10/2022</Text>
</Content>
</AssetCard>
<AssetCard id="lion" textValue="Lion">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1629812456605-4a044aa38fbc?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={899} />
</CardPreview>
<Content>
<Text slot="title">Lion</Text>
<Text slot="description">JPEG • 8/28/2021</Text>
</Content>
</AssetCard>
<AssetCard id="mountain" textValue="Mountain Sunrise">
<CardPreview>
<Image src="https://images.unsplash.com/photo-1722172118908-1a97c312ce8c?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={900} />
</CardPreview>
<Content>
<Text slot="title">Mountain Sunrise</Text>
<Text slot="description">PNG • 3/15/2015</Text>
</Content>
</AssetCard>
</CardView>
<p className={style({font: 'body'})}>selectedKeys: {selectedKeys === 'all' ? 'all' : [...selectedKeys].join(', ')}</p>
</div>
);
}
Item actions
In addition to selection, some collection components support item actions via the onAction prop. When nothing is selected, clicking, tapping, or pressing the Enter key triggers the item action. Items may be selected using the checkbox, or by pressing the Space key. When at least one item is selected, clicking or tapping a row toggles the selection.
On touch devices, actions are the primary tap interaction. Long pressing enters selection mode, which temporarily swaps the selection behavior to where tapping toggles the selection. Deselecting all items exits selection mode and reverts the selection behavior back to where tapping triggers the action. Keyboard behaviors are unaffected.
import {CardView, AssetCard, CardPreview, Image, Content, Text} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
<CardView
aria-label="Nature photos"
selectionMode="multiple"
styles={style({width: 'full', height: 320})}
>
<AssetCard
id="desert"
textValue="Desert Sunset"
onAction={() => alert('Opening Desert Sunset')}>
<CardPreview>
<Image src="https://images.unsplash.com/photo-1705034598432-1694e203cdf3?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={400} />
</CardPreview>
<Content>
<Text slot="title">Desert Sunset</Text>
<Text slot="description">PNG • 2/3/2024</Text>
</Content>
</AssetCard>
<AssetCard
id="hiking"
textValue="Hiking Trail"
onAction={() => alert('Opening Hiking Trail')}>
<CardPreview>
<Image src="https://images.unsplash.com/photo-1722233987129-61dc344db8b6?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={900} />
</CardPreview>
<Content>
<Text slot="title">Hiking Trail</Text>
<Text slot="description">JPEG • 1/10/2022</Text>
</Content>
</AssetCard>
<AssetCard
id="lion"
textValue="Lion"
onAction={() => alert('Opening Lion')}>
<CardPreview>
<Image src="https://images.unsplash.com/photo-1629812456605-4a044aa38fbc?q=80&w=600&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" width={600} height={899} />
</CardPreview>
<Content>
<Text slot="title">Lion</Text>
<Text slot="description">JPEG • 8/28/2021</Text>
</Content>
</AssetCard>
</CardView>
In dynamic collections, it may be more convenient to use the onAction prop at the collection level instead of on individual items. This receives the id of the pressed item.
import {CardView, AssetCard, CardPreview, Image, Content, Text} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
<CardView
aria-label="Nature photos"
selectionMode="multiple"
onAction={id => alert(`Opening ${id}`)}
styles={style({width: 'full', height: 320})}
items={photos}>
{photo => (
<AssetCard id={photo.id} textValue={photo.title}>
<CardPreview>
<Image src={photo.image} width={photo.width} height={photo.height} />
</CardPreview>
<Content>
<Text slot="title">{photo.title}</Text>
<Text slot="description">{photo.description}</Text>
</Content>
</AssetCard>
)}
</CardView>
Disabled items
An item can be disabled with the isDisabled prop. By default, disabled items are not focusable, selectable, or actionable. When disabledBehavior="selection", only selection is disabled.
import {CardView, AssetCard, CardPreview, Image, Content, Text} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
<CardView
aria-label="Pokemon"
selectionMode="multiple"
styles={style({width: 'full', height: 320})}
>
<AssetCard id="charizard" textValue="Charizard">
<CardPreview>
<Image src="https://img.pokemondb.net/sprites/home/normal/2x/avif/charizard.avif" />
</CardPreview>
<Content>
<Text slot="title">Charizard</Text>
<Text slot="description">Fire, Flying • Level 67</Text>
</Content>
</AssetCard>
<AssetCard id="blastoise" textValue="Blastoise">
<CardPreview>
<Image src="https://img.pokemondb.net/sprites/home/normal/2x/avif/blastoise.avif" />
</CardPreview>
<Content>
<Text slot="title">Blastoise</Text>
<Text slot="description">Water • Level 56</Text>
</Content>
</AssetCard>
<AssetCard id="venusaur" textValue="Venusaur" isDisabled>
<CardPreview>
<Image src="https://img.pokemondb.net/sprites/home/normal/2x/avif/venusaur.avif" />
</CardPreview>
<Content>
<Text slot="title">Venusaur</Text>
<Text slot="description">Grass, Poison • Level 83</Text>
</Content>
</AssetCard>
<AssetCard id="pikachu" textValue="Pikachu">
<CardPreview>
<Image src="https://img.pokemondb.net/sprites/home/normal/2x/avif/pikachu.avif" />
</CardPreview>
<Content>
<Text slot="title">Pikachu</Text>
<Text slot="description">Electric • Level 100</Text>
</Content>
</AssetCard>
</CardView>
In dynamic collections, it may be more convenient to use the disabledKeys prop at the collection level instead of isDisabled on individual items. This accepts a list of item ids that are disabled.
import {CardView, AssetCard, CardPreview, Image, Content, Text} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
<CardView
aria-label="Pokemon"
selectionMode="multiple"
disabledKeys={[3]}
styles={style({width: 'full', height: 320})}
items={items}>
{item => (
<AssetCard id={item.id} textValue={item.name}>
<CardPreview>
<Image src={`https://img.pokemondb.net/sprites/home/normal/2x/avif/${item.name.toLowerCase()}.avif`} />
</CardPreview>
<Content>
<Text slot="title">{item.name}</Text>
<Text slot="description">{item.type} • Level {item.level}</Text>
</Content>
</AssetCard>
)}
</CardView>