Selectable tables with batch actions
Selectable tables allow users to select one or more rows simultaneously and perform batch actions against the selected rows. A batch action is any action that can be performed against any selectable row of a table.
Individual row selection
To toggle the selection of an individual row, users can use the Checkbox in the first column which should have the row heading ‘Select’.
Selected rows have a solid action coloured outline, which is achieved by setting selected={true}
on the TableRow
component.
() => { const [isRowSelected, setIsRowSelected] = React.useState(false); return ( <TableWrapper> <Table> <TableHead> <TableRow> <TableHeader>Select</TableHeader> <TableHeader scope="col">Reference</TableHeader> <TableHeader scope="col">Date submitted</TableHeader> <TableHeader scope="col">Actions</TableHeader> </TableRow> </TableHead> <TableBody> <TableRow selected={isRowSelected}> <TableCell> <Checkbox size="sm" checked={isRowSelected} onChange={() => setIsRowSelected((x) => !x)} > <VisuallyHidden>Select row</VisuallyHidden> </Checkbox> </TableCell> <TableCell as="th" scope="row" fontWeight="bold"> <TextLink href="#">REF-AB3CD4EF</TextLink> </TableCell> <TableCell>20/06/2024</TableCell> <TableCell> <Flex gap={1}> <TextLink href="#">Download</TextLink> <TextLink href="#">Delete</TextLink> </Flex> </TableCell> </TableRow> </TableBody> </Table> </TableWrapper> ); };
Selecting all rows
To toggle the selection of all rows at once, users can use the ‘Select all rows’ checkbox above the table. The ‘Select all rows’ checkbox should always be placed just above the table element, with a border to divide the two elements.
Do not use TableCaption
component in combination with the ‘Select all rows’ checkbox as the caption will sit in between the checkbox and table. Instead, place a Heading component above the Checkbox and connect the heading and table using aria-labelledby
. For more information, please refer to the Labels and headings section of the table documentation.
Unlike the checkboxes in each row, the ‘Select all rows’ checkbox above the table has three states: checked, unchecked, and indeterminate. When some, but not all, rows are selected, the ‘Select all rows’ checkbox should enter an indeterminate state. This visual indicator signals to users that not all rows are selected and allows them to toggle all rows on or off with a single click.
() => { const [selectedRows, setSelectedRows] = React.useState([]); function toggleRowSelection(rowIdx) { if (selectedRows.includes(rowIdx)) { setSelectedRows((r) => r.filter((s) => s !== rowIdx)); } else { setSelectedRows((r) => [...r, rowIdx]); } } const anyRowsChecked = selectedRows.length > 0; const allRowsChecked = selectedRows.length === 3; function toggleAllRows() { if (anyRowsChecked) { setSelectedRows([]); } else { setSelectedRows([0, 1, 2]); } } return ( <Stack> <Box paddingBottom={0.75} paddingLeft={0.75} borderBottom> <Checkbox size="sm" checked={allRowsChecked} indeterminate={anyRowsChecked && !allRowsChecked} onChange={() => toggleAllRows()} > Select all rows </Checkbox> </Box> <TableWrapper> <Table> <TableHead> <TableRow> <TableHeader>Select</TableHeader> <TableHeader scope="col">Reference</TableHeader> <TableHeader scope="col">Date submitted</TableHeader> <TableHeader scope="col">Actions</TableHeader> </TableRow> </TableHead> <TableBody> {Array.from(new Array(3).keys()).map((idx) => { const isRowSelected = selectedRows.includes(idx); return ( <TableRow selected={isRowSelected}> <TableCell> <Checkbox size="sm" checked={isRowSelected} onChange={() => toggleRowSelection(idx)} > <VisuallyHidden>Select</VisuallyHidden> </Checkbox> </TableCell> <TableCell as="th" scope="row" fontWeight="bold"> <TextLink href="#">REF-AB3CD4EF</TextLink> </TableCell> <TableCell>20/06/2024</TableCell> <TableCell> <Flex gap={1}> <TextLink href="#">Download</TextLink> <TextLink href="#">Delete</TextLink> </Flex> </TableCell> </TableRow> ); })} </TableBody> </Table> </TableWrapper> </Stack> ); };
Batch actions
When a data table has a set of actions that can be performed against every selectable row, those actions can be applied as a batch. Batch actions can increase user efficiency by allowing an action to be applied to multiple items simultaneously. This is far more efficient than repeatedly applying the same action to multiple table rows one at a time.
Once a row from the table has been selected, the TableBatchActionsBar
component is positioned sticky at the bottom of the table, presenting a set of possible actions to apply to all selected rows.
Only small Buttons should be placed inside of the TableBatchActionsBar
component. When pressed, these buttons should trigger Drawers that contain the form related to the action.
Do
- ensure each batch action can be performed on every selectable row
- include a ‘Cancel’ button at the end of the batch actions button group
- use small buttons for batch actions
Don’t
- include batch actions that can not be performed on every selectable row
- place form components inside the
TableBatchActionsBar
component, as they can introduce inconsistencies as well as complexities related to validation and submission.
() => { const [selectedRows, setSelectedRows] = React.useState([]); function toggleRowSelection(rowIdx) { if (selectedRows.includes(rowIdx)) { setSelectedRows((r) => r.filter((s) => s !== rowIdx)); } else { setSelectedRows((r) => [...r, rowIdx]); } } const anyRowsChecked = selectedRows.length > 0; const allRowsChecked = selectedRows.length === 3; function toggleAllRows() { if (anyRowsChecked) { setSelectedRows([]); } else { setSelectedRows([0, 1, 2]); } } return ( <Stack gap={0.5}> <Stack> <Box paddingBottom={0.75} paddingLeft={0.75} borderBottom> <Checkbox size="sm" checked={allRowsChecked} indeterminate={anyRowsChecked && !allRowsChecked} onChange={() => toggleAllRows()} > Select all rows </Checkbox> </Box> <TableWrapper> <Table> <TableHead> <TableRow> <TableHeader>Select</TableHeader> <TableHeader scope="col">Reference</TableHeader> <TableHeader scope="col">Date submitted</TableHeader> <TableHeader scope="col">Actions</TableHeader> </TableRow> </TableHead> <TableBody> {Array.from(new Array(3).keys()).map((idx) => { const isRowSelected = selectedRows.includes(idx); return ( <TableRow selected={isRowSelected}> <TableCell> <Checkbox size="sm" checked={isRowSelected} onChange={() => toggleRowSelection(idx)} > <VisuallyHidden>Select</VisuallyHidden> </Checkbox> </TableCell> <TableCell as="th" scope="row" fontWeight="bold"> <TextLink href="#">REF-AB3CD4EF</TextLink> </TableCell> <TableCell>20/06/2024</TableCell> <TableCell> <Flex gap={1}> <TextLink href="#">Download</TextLink> <TextLink href="#">Delete</TextLink> </Flex> </TableCell> </TableRow> ); })} </TableBody> </Table> </TableWrapper> </Stack> {selectedRows.length > 0 ? ( <TableBatchActionsBar> <TableBatchActionsTitle> Apply action to {selectedRows.length} items </TableBatchActionsTitle> <ButtonGroup> <Button variant="secondary" size="sm"> Download </Button> <Button variant="secondary" size="sm"> Delete </Button> <Button variant="tertiary" size="sm" onClick={toggleAllRows}> Cancel </Button> </ButtonGroup> </TableBatchActionsBar> ) : null} </Stack> ); };
Related components
- Button – A button communicates an action to a user and indicates what will happen next.
- Checkbox – Checkboxes allow users to select one or more options from a list.
- Drawer – A drawer is a panel that slides in from the right side of the screen. The Drawer is overlayed on top of the main area of the page to capture the user’s attention while keeping the context of the current task.
- Table – Tables help make complex information easier to scan and compare. Use tables for exact values or information that would be hard to read in body text.