Agriculture Design System
Beta
Design System for the Export Service

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.

View in StorybookView in Github

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.

Open in Playroom, opens in a new tab
() => {
	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.

View an example in Storybook

() => {
	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>
	);
};
  • 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.