Skip to content

Components

Table

Composable tables: a glass panel that scrolls, sticky headers, first column and totals, sortable columns, rows reordered by drag and drop (into groups and under other rows), and virtual rows for long lists.

EntryCategoryAmount
Rent MonthlyHousing−$1,850
Paycheck MonthlySalary+$8,450
Weekly shop Groceries−$164

Import

import { TableContainer, Table, TableHead, TableCell, TableFooter, TableFooterCell, TableSpacerRow, TableSelectCell, TableSelectHead, SelectionBar, TableSwipeRow, TableSwipeAction } from '@rinzai/zen';

Examples

Sortable

Click a heading to sort by it, again to reverse, a third time to clear. The header stays put as the rows scroll.

Month
Oct 202619,1881,800220,812
Nov 202619,3321,656201,479
Dec 202619,4771,511182,002
Jan 202719,6231,365162,379
Feb 202719,7711,218142,608
Mar 202719,9191,070122,689
Apr 202720,068920102,621
May 202720,21977082,403
Jun 202720,37061862,032
Jul 202720,52346541,509
Aug 202720,67731120,832
Sep 202720,8321560

Reorder

Rows reordered by drag and drop (SortableTree, with asChild on the table and its rows). Drag a row by its grip within its category, into the other one, into an open sub-item (Eating out), or under another row (drag sideways a level in or out); or Tab to a grip and use Space and the arrow keys. Categories stay at the top level and can be reordered too. The first column stays in view as it scrolls sideways.

ItemJulAugSepOct
Home
Rent$1800$1800$1800$1800
Electricity$92$88$75$70
Internet$60$60$60$60
Food
Groceries$420$395$410$430
Eating out$180$150$210$160
Coffee$42$38$45$40
Takeaway$64$50$80$55

Selectable

Pick rows (shift-click for a run of them), then act on them all from the bar.

EntryAmount
Weekly shop−$165
Farmers market−$42
Train pass−$92
Dinner with Sam−$78
Birthday lunch−$92

Swipe

On a phone, swipe a row left for its actions; with a keyboard, tab to them.

EntryAmount
Weekly shop−$165
Farmers market−$42
Train pass−$92
Dinner with Sam−$78

Detail

Open an entry and its row grows into its details (a view transition, with share="zen-morph").

EntryDateAmount
Oct 4−$165.20
Oct 2−$92.00
Oct 1+$4,200.00

Virtual

10,000 rows, but only the ones in view (plus a few either side) are rendered: useVirtualList with spacer rows.

#DatePayeeAmount
1Jan 1Grocer$1.50
2Jan 1Utilities$80.69
3Jan 1Cinema$159.88
4Jan 1Bookshop$39.07
5Jan 1Pharmacy$118.26
6Jan 1Cafe$197.45
7Jan 1Landlord$76.64
8Jan 1Metro$155.83
9Jan 1Grocer$35.02
10Jan 1Utilities$114.21
11Jan 1Cinema$193.40
12Jan 1Bookshop$72.59
13Jan 1Pharmacy$151.78
14Jan 1Cafe$30.97
15Jan 1Landlord$110.16
16Jan 1Metro$189.35
17Jan 1Grocer$68.54
18Jan 1Utilities$147.73

API reference

<TableContainer>

The panel a table scrolls in: glass with an edge that catches the pointer light, like a Card. Give it a max height (className) to scroll vertically; it scrolls sideways when the table is wider. ref and scroll events are the scrolling element's. With label, it's a focusable region, so keyboard users can scroll it too.

PropTypeDefault
label

Accessible name; makes the scroll area a focusable region.

string—

<Table>

The <table>. Put it in a TableContainer for the panel and scrolling.

No props of its own; it accepts the props of the element it renders.

<TableHead>

Column heading: small spaced caps (TableHeader keeps the row at the top). With onSort, it's a button that shows the direction and announces it (aria-sort).

PropTypeDefault
numeric

Aligned to the end, for numbers.

boolean—
onSort

Makes the heading a sort button.

(() => void)—
sortDirection
SortDirection—
sticky

Stay in view when the table scrolls sideways.

"start"—

<TableCell>

A cell: hairline below, lit with its row on hover. sticky="start" keeps it in view when scrolling sideways.

PropTypeDefault
numeric

Aligned to the end, tabular digits.

boolean—
sticky

Stay in view when the table scrolls sideways.

"start"—

<TableFooter>

Rows that stay at the bottom while the body scrolls (totals, balances).

No props of its own; it accepts the props of the element it renders.

<TableFooterCell>

A footer cell: sits on the card, above a hairline. Use inside TableFooter.

PropTypeDefault
numeric

Aligned to the end, tabular digits.

boolean—
sticky

Stay in view when the table scrolls sideways.

"start"—

<TableSpacerRow>

An empty row of a given height, standing in for rows scrolled out of view (see useVirtualList).

PropTypeDefault
colSpanRequired
number—
heightRequired
number—

<TableSelectCell>

A row's checkbox cell; spread rowProps(key) from useSelection into it.

PropTypeDefault
checkedRequired
boolean—
labelRequired

The checkbox's name, e.g. "Select Rent".

string—
onSelectRequired

With range when shift was held.

(on: boolean, range: boolean) => void—

<TableSelectHead>

The header's checkbox, for every row at once; spread allProps() from useSelection into it.

PropTypeDefault
checkedRequired
boolean—
indeterminateRequired
boolean—
onSelectRequired
(on: boolean) => void—
label
string—

<SelectionBar>

What to do with the selected rows: their count, your actions, and a × to clear the selection. Floats at the bottom of the screen while any are selected (move it with className).

PropTypeDefault
countRequired
number—
onClearRequired
() => void—
children

The actions, e.g. buttons to delete or re-tag them.

ReactNode—
className
string—
clearLabel
string—
label

The count as text (default "3 selected").

((count: number) => string)—

<TableSwipeRow>

A row whose actions sit behind it at its end: swipe it left to reveal them (right, in right-to-left layouts), back (or tap the row) to put them away. They also come out when focused, so the keyboard reaches them. Use it in a TableContainer.

PropTypeDefault
actionsRequired

What's behind the row: TableSwipeActions, e.g. Edit and Delete.

ReactNode—
selected
boolean—

<TableSwipeAction>

One of a swipe row's actions: fills the row's height, flush with its end.

PropTypeDefault
tone
"default" | "negative"default