Table
This is a headless table that the user can operate. The keyboard moves through it like a grid. The user can select rows and can sort a column with a trigger in its header. You assemble the table from parts.
| Service | Owner | Status |
|---|---|---|
| Checkout API | Infra | Healthy |
| Identity Worker | Platform | Rolling |
| Billing Queue | Finance | Healthy |
| Session Cache | Platform | Degraded |
| Audit Trail | Security | Rolling |
| Total | 5 services |
Anatomy
Table.Root makes an interactive grid above native table markup. Put each header cell in a Table.Column. That part has no DOM of its own — it only registers the column. To make a column sortable or resizable, put a Table.SortTrigger or a Table.ColumnResizer in its Table.ColumnHeaderCell.
<script>
import { Table } from '@human-kit/ui';
</script>
<Table.Root aria-label="Users table">
<Table.Header>
<Table.Row>
<Table.Column id="email" rowHeader>
<Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
</Table.Column>
<Table.Column id="group">
<Table.ColumnHeaderCell>Group</Table.ColumnHeaderCell>
</Table.Column>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row id="danilo">
<Table.Cell>danilo@example.com</Table.Cell>
<Table.Cell>Developer</Table.Cell>
</Table.Row>
<Table.EmptyState>No users found.</Table.EmptyState>
</Table.Body>
</Table.Root>Row selection
Set selectionMode="multiple" or selectionMode="single", and give each body row an id. Put Table.Checkbox and Table.CheckboxIndicator in a cell to make the control that the user sees. The checkbox in the header selects all of the rows, and it shows an indeterminate state. The disabledKeys prop keeps a row in the table, but the user cannot select it.
| Group | ||
|---|---|---|
| danilo@example.com | Developer | |
| zahra@example.com | Admin | |
| jasper@example.com | Developer | |
| marta@example.com | Support | |
| nora@example.com | Finance |
Keyboard navigation
The keyboardNavigation prop sets how far the roving tab stop goes into the body. The default is "grid", the full ARIA grid pattern, where each body cell can take the focus. This has a cost. Each cell registers itself and calculates its own focus state. A virtualized table does this again for each row that it makes while the user scrolls.
"grid"— the arrow keys move from cell to cell. Use this mode when the user works in the body, and does not only read it."row"— the row is the only element in the body that takes the focus. The arrow keys move from row to row, theEnterkey presses a row, and theSpacekey selects a row. There is one focus target for each row, not one for each cell. An element in a body cell that can take the focus becomes a usual tab stop, for example aTable.Checkboxor a link. The roving focus does not go to it."none"— the keyboard does not go into the body. Use this mode only when nothing in the body does an action. A row that obeys a click needs an equivalent keyboard operation.
In each mode, the header keeps its own cell navigation. Thus the sort control and the resize control stay available. The header is one row, thus its cost is very small.
Pagination
The table does not do the pagination. Cut the data before you make the table, and control the current page from your own controls or from the state of your application.
Usage guidelines
- Use
Table.Rootas the container with the state for the focus, the selection, and the sort. Give it anaria-labelor anaria-labelledbyattribute. - Put each header cell in a
Table.Column, thus the table can register stable data about the column.Table.Columnmakes no DOM of its own. - Use
selectedKeysandonSelectionChangewhen your own code controls the selection. UsedefaultSelectedKeysfor the initial selection when the component controls it. - Use
selectionBehavior="toggle"to let the user remove the selection of a selected row. Use"replace"to keep a selected row selected when the user presses it again. - Use
sortDescriptorandonSortChangewhen your own code controls the sort. UsedefaultSortDescriptorfor the initial sort when the component controls it. ATable.SortTriggerin a header cell makes that column sortable. - Use
columnWidthsandonColumnWidthsChangewhen your own code controls the widths of the columns. A width can be in px, in%, or infr. Put aTable.ColumnResizerin aTable.ColumnHeaderCellto make that column resizable. - Give a utility column, for example a column of selection checkboxes, an explicit
width,minWidth, andmaxWidthon itsTable.Column. Thus a resize of a different column does not change its space. - Use
Table.EmptyStateinTable.Body. Do not make your own body content with a condition. - Use
Table.InteractiveCellfor a body cell that contains its own controls that take the focus.
Accessibility
Table.Rootmakes an interactivegridabove native table markup. The keyboard operation uses a rovingtabindexacross the header cells and the body cells.keyboardNavigation="row"moves that roving tab stop from the cells to the rows. This is a permitted grid focus pattern."none"removes the tab stop from the body, thus use it only for a table where the rows do nothing.- A body row can also take the focus when the horizontal navigation goes past the start or the end of the row. If the user continues to push the left key or the right key, the focus goes to the cell at the opposite edge.
- A body cell in the first column becomes a
rowheaderwhen its column hasrowHeader. - A disabled row stays in the table and the user cannot select it. The focus does not stop on it.
Table.SortTriggermakes the trigger button, but the header cell stays the roving focus target. The component writes each sort change into a polite live region for a screen reader.- A column resize handle is a keyboard-accessible separator. Push
Enterto start the resize mode. Use the horizontal arrow keys to change the width. PushHomefor the minimum width. PushEndfor the automatic width. PushEnteragain to leave the resize mode.
API reference
Root
The table container with the state. It makes an interactive grid above native table markup. It controls the focus, the selection, the sort, the column widths, and the column visibility.
* required. Native HTML attributes of the underlying element are also accepted.
Column
The data of a column. It makes no DOM of its own. It registers the column, and it must contain one Table.ColumnHeaderCell.
* required. Native HTML attributes of the underlying element are also accepted.
Header
The head section of the table. It contains the header row.
* required. Native HTML attributes of the underlying element are also accepted.
Body
The body section of the table. It contains the data rows and the empty state.
* required. Native HTML attributes of the underlying element are also accepted.
EmptyState
The row that the body shows while the table has no data rows. Use it, and do not make your own body content with a condition.
* required. Native HTML attributes of the underlying element are also accepted.
Footer
The foot section of the table, for the summary rows.
* required. Native HTML attributes of the underlying element are also accepted.
Row
One row of the table. Give each body row an id, for the selection, the focus movement, and the row actions.
* required. Native HTML attributes of the underlying element are also accepted.
ColumnHeaderCell
One header cell. It stays the roving-focus target of its column, also when it contains a sort trigger or a resize handle.
* required. Native HTML attributes of the underlying element are also accepted.
SortTrigger
The button that sorts. Put it in a header cell to make that column sortable. It changes the sortDescriptor of the root.
* required. Native HTML attributes of the underlying element are also accepted.
ColumnResizer
A handle that changes the width, and the keyboard can operate it. Put it in a header cell to let the user change the width of that column.
* required. Native HTML attributes of the underlying element are also accepted.
Checkbox
A selection control, in a header cell or in a body cell. In a header cell, it is in the DOM only when selectionMode is 'multiple'. When the selection is off, it is in the DOM nowhere.
* required. Native HTML attributes of the underlying element are also accepted.
CheckboxIndicator
The mark in a table checkbox. Use it to make your own marks for the checked state and the indeterminate state.
* required. Native HTML attributes of the underlying element are also accepted.
Cell
A cell of the body or of the foot, in the grid keyboard operation. A body cell in the first column becomes a rowheader when its column has rowHeader.
* required. Native HTML attributes of the underlying element are also accepted.
InteractiveCell
A body cell for content that has its own controls. While the focus is on the cell, the grid keyboard operation continues. While the focus is on a control in the cell, that control gets the keys.
* required. Native HTML attributes of the underlying element are also accepted.
The same parts also give you the more complex patterns. These are the visibility of a column, fixed and pinned columns, a header that stays at the top, a column resize, and row actions. The API reference above has the props for them.