Agriculture Design System
Beta
Design System for the Export Service

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.

View in FigmaView in StorybookView in Github
import { ... } from '@ag.ds-next/react/table';
Open in Playroom, opens in a new tab
<TableWrapper>
	<Table>
		<TableCaption>
			Population of Australian states and territories, December 2015
		</TableCaption>
		<TableHead>
			<TableRow>
				<TableHeader scope="col">Location</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Population
				</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Change over previous year %
				</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Change over previous decade %
				</TableHeader>
			</TableRow>
		</TableHead>
		<TableBody>
			<TableRow>
				<TableCell>New South Wales</TableCell>
				<TableCell textAlign="right">7,670,700</TableCell>
				<TableCell textAlign="right">3.1%</TableCell>
				<TableCell textAlign="right">12.9%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Victoria</TableCell>
				<TableCell textAlign="right">5,996,400</TableCell>
				<TableCell textAlign="right">2.5%</TableCell>
				<TableCell textAlign="right">9.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Queensland</TableCell>
				<TableCell textAlign="right">4,808,800</TableCell>
				<TableCell textAlign="right">1.7%</TableCell>
				<TableCell textAlign="right">13.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Western Australia</TableCell>
				<TableCell textAlign="right">2,603,900</TableCell>
				<TableCell textAlign="right">2.3%</TableCell>
				<TableCell textAlign="right">11.6%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>South Australia</TableCell>
				<TableCell textAlign="right">1,702,800</TableCell>
				<TableCell textAlign="right">2.0%</TableCell>
				<TableCell textAlign="right">6.8%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Tasmania</TableCell>
				<TableCell textAlign="right">517,400</TableCell>
				<TableCell textAlign="right">4%</TableCell>
				<TableCell textAlign="right">5.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Northern Territory</TableCell>
				<TableCell textAlign="right">244,400</TableCell>
				<TableCell textAlign="right">1.2%</TableCell>
				<TableCell textAlign="right">4.5%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Australian Capital Territory</TableCell>
				<TableCell textAlign="right">393,000</TableCell>
				<TableCell textAlign="right">2.4%</TableCell>
				<TableCell textAlign="right">9.6%</TableCell>
			</TableRow>
		</TableBody>
	</Table>
</TableWrapper>

Tables are used to scan for information and compare values. They are useful for displaying exact values or information that would be hard to read in body text.

For data tables with a small amount of rows, use the default table. Align text columns and corresponding data cells to the left. When comparing numbers in a column, align data cells and column headers to the right.

Do

  • use for datasets with more than 2 columns
  • use for tabular data - not layouts
  • provide a meaningful caption (heading), or use aria-labelledby to reference a heading
  • keep row heights small.
  • use a simple table structure - complex tables are difficult to navigate.
  • use Pagination to break up tables with many rows
  • align text left and numeric data right
  • only include text, tags, links and status badges as content
  • use a human readable format for numeric data – for example, 1,234,000 instead of 12340000.
  • allow row sorting by using TableHeaderSortable.

Don’t

  • repeat information across cells. If each cell has the same content, add it as the column header – for example, Width (cm).
  • nest tables inside tables
  • use if a list, paragraph of text or diagram will work
  • use to summarise form collection data - use a Summary list
  • add lists, diagrams or images.
  • have too many rows. Use pagination or start another table where logical – for example, an alphabetical list, Table 1: A to D, Table 2: E to H and so on
  • use bespoke styling - follow consistent layouts and styles
  • nest tables inside tables
  • don’t use inside an accorion.

Striped

Use zebra stripes for tables with complex information:

  • 4 or more rows
  • 3 or more columns
  • comparing important or similar information.

You can enable zebra stripes by applying the striped prop.

<TableWrapper>
	<Table striped>
		<TableCaption>
			Population of Australian states and territories, December 2015
		</TableCaption>
		<TableHead>
			<TableRow>
				<TableHeader scope="col">Location</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Population
				</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Change over previous year %
				</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Change over previous decade %
				</TableHeader>
			</TableRow>
		</TableHead>
		<TableBody>
			<TableRow>
				<TableCell>New South Wales</TableCell>
				<TableCell textAlign="right">7,670,700</TableCell>
				<TableCell textAlign="right">3.1%</TableCell>
				<TableCell textAlign="right">12.9%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Victoria</TableCell>
				<TableCell textAlign="right">5,996,400</TableCell>
				<TableCell textAlign="right">2.5%</TableCell>
				<TableCell textAlign="right">9.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Queensland</TableCell>
				<TableCell textAlign="right">4,808,800</TableCell>
				<TableCell textAlign="right">1.7%</TableCell>
				<TableCell textAlign="right">13.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Western Australia</TableCell>
				<TableCell textAlign="right">2,603,900</TableCell>
				<TableCell textAlign="right">2.3%</TableCell>
				<TableCell textAlign="right">11.6%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>South Australia</TableCell>
				<TableCell textAlign="right">1,702,800</TableCell>
				<TableCell textAlign="right">2.0%</TableCell>
				<TableCell textAlign="right">6.8%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Tasmania</TableCell>
				<TableCell textAlign="right">517,400</TableCell>
				<TableCell textAlign="right">4%</TableCell>
				<TableCell textAlign="right">5.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Northern Territory</TableCell>
				<TableCell textAlign="right">244,400</TableCell>
				<TableCell textAlign="right">1.2%</TableCell>
				<TableCell textAlign="right">4.5%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Australian Capital Territory</TableCell>
				<TableCell textAlign="right">393,000</TableCell>
				<TableCell textAlign="right">2.4%</TableCell>
				<TableCell textAlign="right">9.6%</TableCell>
			</TableRow>
		</TableBody>
	</Table>
</TableWrapper>

Custom column width

You can use custom widths based on the expected length of the data under each corresponding column.

<TableWrapper>
	<Table striped>
		<TableCaption>
			Population of Australian states and territories, December 2015
		</TableCaption>
		<TableHead>
			<TableRow>
				<TableHeader scope="col" width="50%">
					Location
				</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Population
				</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Change over previous year %
				</TableHeader>
				<TableHeader textAlign="right" scope="col">
					Change over previous decade %
				</TableHeader>
			</TableRow>
		</TableHead>
		<TableBody>
			<TableRow>
				<TableCell>New South Wales</TableCell>
				<TableCell textAlign="right">7,670,700</TableCell>
				<TableCell textAlign="right">3.1%</TableCell>
				<TableCell textAlign="right">12.9%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Victoria</TableCell>
				<TableCell textAlign="right">5,996,400</TableCell>
				<TableCell textAlign="right">2.5%</TableCell>
				<TableCell textAlign="right">9.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Queensland</TableCell>
				<TableCell textAlign="right">4,808,800</TableCell>
				<TableCell textAlign="right">1.7%</TableCell>
				<TableCell textAlign="right">13.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Western Australia</TableCell>
				<TableCell textAlign="right">2,603,900</TableCell>
				<TableCell textAlign="right">2.3%</TableCell>
				<TableCell textAlign="right">11.6%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>South Australia</TableCell>
				<TableCell textAlign="right">1,702,800</TableCell>
				<TableCell textAlign="right">2.0%</TableCell>
				<TableCell textAlign="right">6.8%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Tasmania</TableCell>
				<TableCell textAlign="right">517,400</TableCell>
				<TableCell textAlign="right">4%</TableCell>
				<TableCell textAlign="right">5.3%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Northern Territory</TableCell>
				<TableCell textAlign="right">244,400</TableCell>
				<TableCell textAlign="right">1.2%</TableCell>
				<TableCell textAlign="right">4.5%</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Australian Capital Territory</TableCell>
				<TableCell textAlign="right">393,000</TableCell>
				<TableCell textAlign="right">2.4%</TableCell>
				<TableCell textAlign="right">9.6%</TableCell>
			</TableRow>
		</TableBody>
	</Table>
</TableWrapper>

Sortable columns

You can use the TableHeaderSortable component to make columns sortable. This component is a wrapper around the TableHeader component and adds the necessary aria attributes to make the column sortable.

You can see a working example of this component in the Search filters patterns.

<TableWrapper>
	<Table striped>
		<TableCaption>
			Population of Australian states and territories, December 2015
		</TableCaption>
		<TableHead>
			<TableRow>
				<TableHeaderSortable
					scope="col"
					sort="ASC"
					width="50%"
					onClick={console.log}
				>
					Location
				</TableHeaderSortable>
				<TableHeaderSortable scope="col" width="50%" onClick={console.log}>
					Population
				</TableHeaderSortable>
			</TableRow>
		</TableHead>
		<TableBody>
			<TableRow>
				<TableCell>Australian Capital Territory</TableCell>
				<TableCell textAlign="right">393,000</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>New South Wales</TableCell>
				<TableCell textAlign="right">7,670,700</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Northern Territory</TableCell>
				<TableCell textAlign="right">244,400</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Queensland</TableCell>
				<TableCell textAlign="right">4,808,800</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>South Australia</TableCell>
				<TableCell textAlign="right">1,702,800</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Tasmania</TableCell>
				<TableCell textAlign="right">517,400</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Victoria</TableCell>
				<TableCell textAlign="right">5,996,400</TableCell>
			</TableRow>
			<TableRow>
				<TableCell>Western Australia</TableCell>
				<TableCell textAlign="right">2,603,900</TableCell>
			</TableRow>
		</TableBody>
	</Table>
</TableWrapper>

Labels and headings

A Table must have some form of caption or heading to describe the data it contains. This can be done using the TableCaption component, or referencing a heading using aria-labelledby.

<Stack gap={1}>
	<H3 id="table-heading">Applications</H3>
	<Text id="table-description">Active establishment registrations</Text>
	<TableWrapper>
		<Table aria-labelledby="table-heading" aria-describedby="table-description">
			<TableHead>
				<TableRow>
					<TableHeader scope="col">Location</TableHeader>
					<TableHeader textAlign="right" scope="col">
						Population
					</TableHeader>
					<TableHeader textAlign="right" scope="col">
						Change over previous year %
					</TableHeader>
					<TableHeader textAlign="right" scope="col">
						Change over previous decade %
					</TableHeader>
				</TableRow>
			</TableHead>
			<TableBody>
				<TableRow>
					<TableCell as="th" scope="row">
						New South Wales
					</TableCell>
					<TableCell textAlign="right">7,670,700</TableCell>
					<TableCell textAlign="right">3.1%</TableCell>
					<TableCell textAlign="right">12.9%</TableCell>
				</TableRow>
				<TableRow>
					<TableCell as="th" scope="row">
						Victoria
					</TableCell>
					<TableCell textAlign="right">5,996,400</TableCell>
					<TableCell textAlign="right">2.5%</TableCell>
					<TableCell textAlign="right">9.3%</TableCell>
				</TableRow>
				<TableRow>
					<TableCell as="th" scope="row">
						Queensland
					</TableCell>
					<TableCell textAlign="right">4,808,800</TableCell>
					<TableCell textAlign="right">1.7%</TableCell>
					<TableCell textAlign="right">13.3%</TableCell>
				</TableRow>
				<TableRow>
					<TableCell as="th" scope="row">
						Western Australia
					</TableCell>
					<TableCell textAlign="right">2,603,900</TableCell>
					<TableCell textAlign="right">2.3%</TableCell>
					<TableCell textAlign="right">11.6%</TableCell>
				</TableRow>
				<TableRow>
					<TableCell as="th" scope="row">
						South Australia
					</TableCell>
					<TableCell textAlign="right">1,702,800</TableCell>
					<TableCell textAlign="right">2.0%</TableCell>
					<TableCell textAlign="right">6.8%</TableCell>
				</TableRow>
				<TableRow>
					<TableCell as="th" scope="row">
						Tasmania
					</TableCell>
					<TableCell textAlign="right">517,400</TableCell>
					<TableCell textAlign="right">4%</TableCell>
					<TableCell textAlign="right">5.3%</TableCell>
				</TableRow>
				<TableRow>
					<TableCell as="th" scope="row">
						Northern Territory
					</TableCell>
					<TableCell textAlign="right">244,400</TableCell>
					<TableCell textAlign="right">1.2%</TableCell>
					<TableCell textAlign="right">4.5%</TableCell>
				</TableRow>
				<TableRow>
					<TableCell as="th" scope="row">
						Australian Capital Territory
					</TableCell>
					<TableCell textAlign="right">393,000</TableCell>
					<TableCell textAlign="right">2.4%</TableCell>
					<TableCell textAlign="right">9.6%</TableCell>
				</TableRow>
			</TableBody>
		</Table>
	</TableWrapper>
</Stack>

‘colSpan’ and ‘rowSpan’

Some tables require cells which represent multiple rows or columns. Instead of repeating the spanning data each time, you can use the colSpan and rowSpan props to specify how a specific cell spans multiple columns and rows, respectively.

<TableWrapper>
	<Table>
		<TableCaption>Items sold December 2024</TableCaption>
		<TableHead>
			<TableRow>
				<TableHeader as="td" colSpan={2}></TableHeader>
				<TableHeader scope="col">Trousers</TableHeader>
				<TableHeader scope="col">Skirts</TableHeader>
				<TableHeader scope="col">Dresses</TableHeader>
				<TableHeader scope="col">Bracelets</TableHeader>
			</TableRow>
		</TableHead>
		<TableBody>
			<TableRow>
				<TableCell as="th" fontWeight="bold" rowSpan="2" scope="rowgroup">
					Australia
				</TableCell>
				<TableCell as="th" fontWeight="bold" scope="row">
					Canberra
				</TableCell>
				<TableCell textAlign="right">80</TableCell>
				<TableCell textAlign="right">12</TableCell>
				<TableCell textAlign="right">43</TableCell>
				<TableCell textAlign="right">36</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" fontWeight="bold" scope="row">
					Sydney
				</TableCell>
				<TableCell textAlign="right">89</TableCell>
				<TableCell textAlign="right">34</TableCell>
				<TableCell textAlign="right">69</TableCell>
				<TableCell textAlign="right">85</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" fontWeight="bold" rowSpan="3" scope="rowgroup">
					Belgium
				</TableCell>
				<TableCell as="th" fontWeight="bold" scope="row">
					Antwerp
				</TableCell>
				<TableCell textAlign="right">56</TableCell>
				<TableCell textAlign="right">22</TableCell>
				<TableCell textAlign="right">43</TableCell>
				<TableCell textAlign="right">72</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" fontWeight="bold" scope="row">
					Brussels
				</TableCell>
				<TableCell textAlign="right">51</TableCell>
				<TableCell textAlign="right">27</TableCell>
				<TableCell textAlign="right">38</TableCell>
				<TableCell textAlign="right">69</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" fontWeight="bold" scope="row">
					Gent
				</TableCell>
				<TableCell textAlign="right">46</TableCell>
				<TableCell textAlign="right">18</TableCell>
				<TableCell textAlign="right">50</TableCell>
				<TableCell textAlign="right">61</TableCell>
			</TableRow>
		</TableBody>
	</Table>
</TableWrapper>

Actions

The actions associated with each table row (e.g. ‘Edit’, ‘Delete’, ‘Download’) should always be positioned in the last column, beneath the table header labeled ‘Actions’.

If each table row can open a page to view more information, the cells in the first column should be displayed as bold clickable links under the table header labeled with the unique identifier.

<TableWrapper>
	<Table>
		<TableCaption>Applications</TableCaption>
		<TableHead>
			<TableRow>
				<TableHeader scope="col">Reference</TableHeader>
				<TableHeader scope="col">Date submitted</TableHeader>
				<TableHeader scope="col">Status</TableHeader>
				<TableHeader scope="col">Actions</TableHeader>
			</TableRow>
		</TableHead>
		<TableBody>
			<TableRow>
				<TableCell as="th" scope="row" fontWeight="bold">
					<TextLink href="#">REF-AB3CD4EF</TextLink>
				</TableCell>
				<TableCell textAlign="right">20/06/2024</TableCell>
				<TableCell textAlign="right">
					<StatusBadge weight="subtle" tone="info" label="In progress" />
				</TableCell>
				<TableCell textAlign="right">
					<Flex gap={1}>
						<TextLink href="#">Edit</TextLink>
						<TextLink href="#">Download</TextLink>
					</Flex>
				</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" scope="row" fontWeight="bold">
					<TextLink href="#">REF-5GH6IJ7K</TextLink>
				</TableCell>
				<TableCell textAlign="right">25/06/2024</TableCell>
				<TableCell textAlign="right">
					<StatusBadge weight="subtle" tone="info" label="In progress" />
				</TableCell>
				<TableCell textAlign="right">
					<Flex gap={1}>
						<TextLink href="#">Edit</TextLink>
						<TextLink href="#">Download</TextLink>
					</Flex>
				</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" scope="row" fontWeight="bold">
					<TextLink href="#">REF-M8NO9PQR</TextLink>
				</TableCell>
				<TableCell textAlign="right">02/07/2024</TableCell>
				<TableCell textAlign="right">
					<StatusBadge weight="subtle" tone="success" label="Completed" />
				</TableCell>
				<TableCell textAlign="right">
					<Flex gap={1}>
						<TextLink href="#">Edit</TextLink>
						<TextLink href="#">Download</TextLink>
					</Flex>
				</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" scope="row" fontWeight="bold">
					<TextLink href="#">REF-S1TU2VWX</TextLink>
				</TableCell>
				<TableCell textAlign="right">05/08/2024</TableCell>
				<TableCell textAlign="right">
					<StatusBadge weight="subtle" tone="info" label="In progress" />
				</TableCell>
				<TableCell textAlign="right">
					<Flex gap={1}>
						<TextLink href="#">Edit</TextLink>
						<TextLink href="#">Download</TextLink>
					</Flex>
				</TableCell>
			</TableRow>
			<TableRow>
				<TableCell as="th" scope="row" fontWeight="bold">
					<TextLink href="#">REF-Y3ZA4B5C</TextLink>
				</TableCell>
				<TableCell textAlign="right">19/10/2024</TableCell>
				<TableCell textAlign="right">
					<StatusBadge weight="subtle" tone="success" label="Completed" />
				</TableCell>
				<TableCell textAlign="right">
					<Flex gap={1}>
						<TextLink href="#">Edit</TextLink>
						<TextLink href="#">Download</TextLink>
					</Flex>
				</TableCell>
			</TableRow>
		</TableBody>
	</Table>
</TableWrapper>

Selectable with batch actions

Selectable tables allow users to select one or more rows simultaneously and perform batch actions against the selected items.

For more information, read the Selectable tables with batch actions pattern.

() => {
	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>
	);
};
  • Summary list Summary list is used to summarise information such as a user’s responses at the end of a form.
  • Search filters Search filters help users find what they’re looking for by displaying options that meet specified criteria.
  • 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.