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.

ServiceOwnerStatus
Checkout API Infra Healthy
Identity Worker Platform Rolling
Billing Queue Finance Healthy
Session Cache Platform Degraded
Audit Trail Security Rolling
Total 5 services
Selection unavailable for this row.

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.

Email Group
danilo@example.com Developer
zahra@example.com Admin
jasper@example.com Developer
marta@example.com Support
nora@example.com Finance
Selection unavailable for this row.

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, the Enter key presses a row, and the Space key 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 a Table.Checkbox or 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.Root as the container with the state for the focus, the selection, and the sort. Give it an aria-label or an aria-labelledby attribute.
  • Put each header cell in a Table.Column, thus the table can register stable data about the column. Table.Column makes no DOM of its own.
  • Use selectedKeys and onSelectionChange when your own code controls the selection. Use defaultSelectedKeys for 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 sortDescriptor and onSortChange when your own code controls the sort. Use defaultSortDescriptor for the initial sort when the component controls it. A Table.SortTrigger in a header cell makes that column sortable.
  • Use columnWidths and onColumnWidthsChange when your own code controls the widths of the columns. A width can be in px, in %, or in fr. Put a Table.ColumnResizer in a Table.ColumnHeaderCell to make that column resizable.
  • Give a utility column, for example a column of selection checkboxes, an explicit width, minWidth, and maxWidth on its Table.Column. Thus a resize of a different column does not change its space.
  • Use Table.EmptyState in Table.Body. Do not make your own body content with a condition.
  • Use Table.InteractiveCell for a body cell that contains its own controls that take the focus.

Accessibility

  • Table.Root makes an interactive grid above native table markup. The keyboard operation uses a roving tabindex across 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 rowheader when its column has rowHeader.
  • A disabled row stays in the table and the user cannot select it. The focus does not stop on it.
  • Table.SortTrigger makes 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 Enter to start the resize mode. Use the horizontal arrow keys to change the width. Push Home for the minimum width. Push End for the automatic width. Push Enter again 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.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-disabled-behavior The configured disabled behavior ('all' | 'selection').
data-focus-visible Present while a focus in the table must be visible (keyboard modality).
data-focus-within Present while focus is inside the table.
data-keyboard-navigation
data-selection-behavior The configured selection behavior ('toggle' | 'replace').
data-selection-mode The configured selection mode ('none' | 'single' | 'multiple').
data-table-column-resizer Marks a handle that changes a column width. If the table has one, the root changes to a fixed table layout.
data-table-implicit-width Present on the col element of a column while the column still has an implicit width. The first width change removes it.

Column

The data of a column. It makes no DOM of its own. It registers the column, and it must contain one Table.ColumnHeaderCell.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Header

The head section of the table. It contains the header row.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-table-header Identifies the header element.

Body

The body section of the table. It contains the data rows and the empty state.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-empty Present while the body has no rows.
data-table-body Identifies the body element.
data-virtual-spacer Identifies the top spacer row or the bottom spacer row of the virtualization ('top' | 'bottom').

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.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-empty Identifies the empty state row.

The foot section of the table, for the summary rows.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-table-footer Identifies the footer element.

Row

One row of the table. Give each body row an id, for the selection, the focus movement, and the row actions.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-actionable Present when the row runs a row action on activation.
data-disabled Present when the row is disabled.
data-focus-visible Present while the row has the focus and the focus must be visible.
data-focus-visible-within Present while an element in the row has a focus that must be visible.
data-focus-within Present while focus is inside the row.
data-focused Present while the row itself has focus.
data-selected Present while the row is selected.
data-selection-disabled Present when the user cannot select the row.

ColumnHeaderCell

One header cell. It stays the roving-focus target of its column, also when it contains a sort trigger or a resize handle.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-column-index Index of the cell's column among visible columns.
data-focus-visible Present while the cell has the focus and the focus must be visible.
data-focus-visible-within Present while an element in the cell has a focus that must be visible.
data-focus-within Present while focus is inside the cell.
data-focused Present while the cell has focus.
data-pin-edge The edge the owning column is pinned to ('left' | 'right').
data-pinned Present when the owning column is pinned.
data-sort-direction The sort direction while this column is the sort column ('ascending' | 'descending').
data-sortable Present when the owning column is sortable.

SortTrigger

The button that sorts. Put it in a header cell to make that column sortable. It changes the sortDescriptor of the root.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-sort-direction The sort direction while the owning column is the sort column ('ascending' | 'descending').
data-sorted Present while the owning column is the active sort column.
data-table-sort-trigger Identifies the sort trigger element.

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.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-focus-visible Present while the handle has the focus and the focus must be visible.
data-focused Present while the handle has focus.
data-resizable Present when the user can change the width of the owning column now.
data-resizable-direction The direction of the width change from this handle. Today it is always 'right'.
data-resizing Present while the user changes the width with a pointer or with the keyboard.
data-table-column-resizer Identifies the resize handle element.
data-testid Identifies the hidden live region for the width status ('column-resize-status'). Do not write styles for it.

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.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-table-checkbox Identifies the checkbox element.

CheckboxIndicator

The mark in a table checkbox. Use it to make your own marks for the checked state and the indeterminate state.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-checkbox-indicator Identifies the indicator element.
data-checked Present while the checkbox is checked.
data-disabled Present when the checkbox is disabled.
data-focus-visible Present while the focus must be visible (keyboard modality).
data-focused Present while the checkbox has focus.
data-indeterminate Present while the checkbox is indeterminate, because only some rows are selected.
data-pressed Present while the checkbox is actively pressed.
data-unchecked Present while the checkbox is unchecked (with `forceMount`).

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.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-actionable Present when the row of the cell runs a row action on activation.
data-column-index Index of the cell's column among visible columns.
data-disabled Present when the cell's row is disabled.
data-focus-visible Present while the cell has the focus and the focus must be visible.
data-focused Present while the cell has focus.
data-pin-edge The edge the owning column is pinned to ('left' | 'right').
data-pinned Present when the owning column is pinned.
data-row-selected Present while the cell's row is selected.
data-selection-disabled Present when the user cannot select the row of the cell.

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.

Prop Type Default

* required. Native HTML attributes of the underlying element are also accepted.

Data attribute Description
data-actionable Present when the row of the cell runs a row action on activation.
data-column-index Index of the cell's column among visible columns.
data-disabled Present when the cell's row is disabled.
data-focus-visible Present while the cell has the focus and the focus must be visible.
data-focused Present while the cell has focus.
data-pin-edge The edge the owning column is pinned to ('left' | 'right').
data-pinned Present when the owning column is pinned.
data-row-selected Present while the cell's row is selected.
data-selection-disabled Present when the user cannot select the row of the cell.

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.