A table puts records in rows and their fields in columns so they can be scanned and compared. Sorting and row selection are what this component gives you. Search, filtering and bulk operations are not built in, and nothing in PDS supplies them.
Anatomy
Section titled “Anatomy”-
Header row △ the top row of column labels; can include sorting controls and a select-all checkbox, and can be made sticky so the labels stay visible while the body scrolls.NameStatusAda Lovelace Approved Grace Hopper Pending NameStatusAda Lovelace Approved Grace Hopper Pending -
Body row a horizontal group of cells representing a single data record.NameStatusAda Lovelace Approved Grace Hopper Pending NameStatusAda Lovelace Approved Grace Hopper Pending -
Row checkbox (optional) selects one row; the matching checkbox in the header row selects or clears all of them.NameStatusAda Lovelace Approved Grace Hopper Pending NameStatusAda Lovelace Approved Grace Hopper Pending -
Cell one data value, at the intersection of a row and a column.NameStatusAda Lovelace Approved Grace Hopper Pending NameStatusAda Lovelace Approved Grace Hopper Pending -
Row actions (optional) the overflow menu at the end of a row, holding actions that apply to that record.NameStatusAda Lovelace Approved Grace Hopper Pending NameStatusAda Lovelace Approved Grace Hopper Pending
Table is one of a family. It takes size and isLoading and is the <table>
itself; the head, rows, header cells, cells and the layout wrapper are separate
exports with props of their own, and the generated table below covers only the
first. Where a prop named on this page is not in that table, it belongs to one
of the others. size is the one that reaches all of them: sm cuts the padding
in every header cell and every cell, and is what a dense table is made of.
When to use
Section titled “When to use”Use a table when the reader’s question is comparative: which of these is largest, newest, failing. Columns are what make that question answerable, so a set of records with nothing consistent to line up does not gain anything from being put in a grid.
A table that needs many columns, or nested data, has outgrown the grid. Use a list or a Card per record when the content is not structured in consistent columns.
Sorting
Section titled “Sorting”The header cell keeps the sort state itself
Section titled “The header cell keeps the sort state itself”Pass sortable and a sortDirection of asc or desc; the header cell
derives aria-sort from the pair and sets it, along with
role="columnheader". Setting aria-sort by hand duplicates a value the
component already maintains, and the two can then disagree.
The sort caret is hidden until the header is hovered. On a sortable column that has not been sorted yet, nothing visible says it can be — which is worth knowing before deciding a column needs sorting at all, because for a touch or keyboard reader that affordance never appears.
Leave sorting off a column whose order says nothing
Section titled “Leave sorting off a column whose order says nothing”A sortable header is an invitation, and sorting by a free-text note or an opaque identifier answers no question anyone has. The control ends up asking the reader to try it and find out.
Selection
Section titled “Selection”Borrow a row checkbox’s name from the row
Section titled “Borrow a row checkbox’s name from the row”A row checkbox has no label of its own, so point it at the cell that identifies
the record with aria-labelledby. Without it, a screen reader reads a column of
identical unnamed checkboxes, and there is nothing in the row to distinguish
them by. This is one of two places in PDS where a control is allowed to borrow
its name — see Accessibility.
The header checkbox selects and clears every row, and where only some rows are selected it is indeterminate rather than unchecked. Unchecked would be a lie about the state of the table.
Start the row with the checkbox
Section titled “Start the row with the checkbox”It is where the reader’s eye starts, and it keeps the identifying cell — the one the checkbox is named from — adjacent to it.
States
Section titled “States”Tell a loading table apart from an empty one
Section titled “Tell a loading table apart from an empty one”isLoading animates a line under the header row. It does not disable the table,
replace the rows, or say anything to a screen reader: the rows underneath are
the previous result, still readable and now stale.
A table with no rows has to say so in words, and the words are the caller’s:
TableLayout, the wrapper export, renders whatever you pass as nullState.
Whether the column headers stay above that message is showHeader — with it,
the reader can still see what the table would have contained; without it, the
null state replaces the table outright. A table waiting for data and a table
with none have different things to say, and only the second is “No records
found”.
Accessibility
Section titled “Accessibility”Nest the parts as a table
Section titled “Nest the parts as a table”Each export sets its own role — table, rowgroup, row, columnheader,
cell — so the semantics are already there, and they are correct only while the
nesting is. A header cell outside a row, or a row outside the head or body,
announces a role its position contradicts, and a screen reader’s table mode
stops being able to describe where the reader is.
Sticky headers keep working through all of it: sticky on the head, or
isSticky on a single header cell, is position: sticky and nothing more, so
the cell stays in the reading order it started in.
Put a row’s action in a cell, not on the row
Section titled “Put a row’s action in a cell, not on the row”A row accepts onClick and responds by showing a pointer cursor. It does not
become focusable, gain a role, or activate on Enter, so a whole-row click is a
mouse-only affordance. Where the row is meant to be openable, put a link or
button in the identifying cell and let the row’s click be the shortcut.
Content
Section titled “Content”A cell holds one value, and a long one widens the column
Section titled “A cell holds one value, and a long one widens the column”A cell holds one value; anything larger belongs outside the table. TableCell
does not wrap, so long text there does not make a taller row — it makes a wider
column and pushes the columns after it out of view. breakWord turns wrapping
on for one cell, and TableTD, a second exported cell built on the first, has
it on already. Two cell exports that disagree about wrapping is worth checking
before assuming a long value will behave.
Give a cell one job for the same reason: an action button and inline editing in the same cell compete for the same click. Long-form text and embedded interactive components belong on the record’s own page rather than inside its row.
Numbers cannot be right-aligned in a cell
Section titled “Numbers cannot be right-aligned in a cell”A cell is left-aligned and has no prop to change it, so the convention of
right-aligning numbers is not something this component can do. align="right"
exists on the header cell, where it moves the label and the sort caret to the
other side of the cell — which lines a header up with nothing, unless the
numbers under it have been aligned in CSS of your own.
Collect row actions in one column
Section titled “Collect row actions in one column”Align related actions consistently in a dedicated column at the end, and where there are more than two, an overflow Dropdown Menu keeps that column one cell wide. Column headers are where units and formats go — “Amount (USD)” — since the cells below repeat the number and not its meaning.
| Name | Type |
|---|---|
size |
|
isLoading |
|
Was this page helpful?