Table
A list of records in a bordered card, one row per record.
Overview
Records that share columns, where a person compares values down a column.
Records
| Client | Premium |
|---|---|
| Naledi Dlamini | R 1 249.00 |
| Pieter van Wyk | R 880.50 |
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Quotes">
<TableHead>
<TableRow>
<TableHeaderCell>Client</TableHeaderCell>
<TableHeaderCell align="end">Premium</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell tone="strong">Naledi Dlamini</TableCell>
<TableCell align="end" numeric>
R 1 249.00
</TableCell>
</TableRow>
<TableRow>
<TableCell tone="strong">Pieter van Wyk</TableCell>
<TableCell align="end" numeric>
R 880.50
</TableCell>
</TableRow>
</TableBody>
</Table>align="end" numeric cells so the figures line up.| Client | Premium |
|---|---|
| Naledi Dlamini | R 1 249.00 |
| Pieter van Wyk | R 880.50 |
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Quotes">
<TableHead>
<TableRow>
<TableHeaderCell>Client</TableHeaderCell>
<TableHeaderCell>Premium</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Naledi Dlamini</TableCell>
<TableCell>R 1 249.00</TableCell>
</TableRow>
<TableRow>
<TableCell>Pieter van Wyk</TableCell>
<TableCell>R 880.50</TableCell>
</TableRow>
</TableBody>
</Table>Totals
| Line | Spent |
|---|---|
| Groceries | R 4 120.00 |
| Eating out | R 2 980.00 |
| Total | R 7 100.00 |
import {
Table,
TableBody,
TableCell,
TableFoot,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Budget">
<TableHead>
<TableRow>
<TableHeaderCell>Line</TableHeaderCell>
<TableHeaderCell align="end">Spent</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Groceries</TableCell>
<TableCell align="end" numeric>
R 4 120.00
</TableCell>
</TableRow>
<TableRow>
<TableCell>Eating out</TableCell>
<TableCell align="end" numeric>
R 2 980.00
</TableCell>
</TableRow>
</TableBody>
<TableFoot>
<TableRow>
<TableCell>Total</TableCell>
<TableCell align="end" numeric>
R 7 100.00
</TableCell>
</TableRow>
</TableFoot>
</Table>TableFoot after the body: strong cells and a firmer line above them.| Line | Spent |
|---|---|
| Groceries | R 4 120.00 |
| Eating out | R 2 980.00 |
| Total | R 7 100.00 |
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Budget">
<TableHead>
<TableRow>
<TableHeaderCell>Line</TableHeaderCell>
<TableHeaderCell align="end">Spent</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Groceries</TableCell>
<TableCell align="end" numeric>
R 4 120.00
</TableCell>
</TableRow>
<TableRow>
<TableCell>Eating out</TableCell>
<TableCell align="end" numeric>
R 2 980.00
</TableCell>
</TableRow>
<TableRow>
<TableCell tone="strong">Total</TableCell>
<TableCell align="end" numeric tone="strong">
R 7 100.00
</TableCell>
</TableRow>
</TableBody>
</Table><tfoot>.Row status
| Debit order | Status | Amount |
|---|---|---|
| Insurance | Paid | R 1 850.00 |
| Gym | Failed | R 349.00 |
import {
Pill,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Debit orders">
<TableHead>
<TableRow>
<TableHeaderCell>Debit order</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
<TableHeaderCell align="end">Amount</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell tone="strong">Insurance</TableCell>
<TableCell>Paid</TableCell>
<TableCell align="end" numeric>
R 1 850.00
</TableCell>
</TableRow>
<TableRow tone="danger">
<TableCell tone="strong">Gym</TableCell>
<TableCell>
<Pill tone="red">Failed</Pill>
</TableCell>
<TableCell align="end" numeric>
R 349.00
</TableCell>
</TableRow>
</TableBody>
</Table>tone marks the row's status with a stripe and a soft fill, and a Pill in a cell names the status in words.| Debit order | Amount |
|---|---|
| Insurance | R 1 850.00 |
| Gym | R 349.00 |
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Debit orders">
<TableHead>
<TableRow>
<TableHeaderCell>Debit order</TableHeaderCell>
<TableHeaderCell align="end">Amount</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell tone="strong">Insurance</TableCell>
<TableCell align="end" numeric>
R 1 850.00
</TableCell>
</TableRow>
<TableRow tone="danger">
<TableCell tone="strong">Gym</TableCell>
<TableCell align="end" numeric>
R 349.00
</TableCell>
</TableRow>
</TableBody>
</Table>Stripe
- Changed
- Removed
| Page | Status |
|---|---|
| /pricing | Changed |
| /old-landing | Removed |
import {
Legend,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Stack gap="xs">
<Legend
items={[
{ label: "Changed", tone: "warn" },
{ label: "Removed", tone: "danger" },
]}
/>
<Table caption="Pages">
<TableHead>
<TableRow>
<TableHeaderCell>Page</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow stripe="warn">
<TableCell tone="strong">/pricing</TableCell>
<TableCell>Changed</TableCell>
</TableRow>
<TableRow stripe="danger">
<TableCell tone="strong">/old-landing</TableCell>
<TableCell>Removed</TableCell>
</TableRow>
</TableBody>
</Table>
</Stack>stripe marks a row's status by colour alone and keeps its hover; a Legend and a status word in the row say what each colour means.| Page |
|---|
| /pricing |
| /old-landing |
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Pages">
<TableHead>
<TableRow>
<TableHeaderCell>Page</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow stripe="warn">
<TableCell tone="strong">/pricing</TableCell>
</TableRow>
<TableRow stripe="danger">
<TableCell tone="strong">/old-landing</TableCell>
</TableRow>
</TableBody>
</Table>Actions column
| Client | Actions |
|---|---|
| Naledi Dlamini |
import {
Button,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Clients">
<TableHead>
<TableRow>
<TableHeaderCell>Client</TableHeaderCell>
<TableHeaderCell align="end" srLabel="Actions" />
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell tone="strong">Naledi Dlamini</TableCell>
<TableCell align="end">
<Button variant="outline" size="sm">
Open
</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>srLabel: the head keeps its column and a screen reader hears "Actions".| Client | |
|---|---|
| Naledi Dlamini |
import {
Button,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@hackcove/ui";
<Table caption="Clients">
<TableHead>
<TableRow>
<TableHeaderCell>Client</TableHeaderCell>
<TableHeaderCell align="end" />
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell tone="strong">Naledi Dlamini</TableCell>
<TableCell align="end">
<Button variant="outline" size="sm">
Open
</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>Usage
- Compose a
TablefromTableHead,TableBodyand, for totals,TableFoot. ATableHeadholds oneTableRowofTableHeaderCells; give theTableacaptionthat names it. - Right-align a column of amounts with
align="end"on its header and its cells, and setnumericon the cells so the figures line up. A code or a type ismono. - A cell's
toneisdefault,strongfor the name of the row,mutedfor the secondary column, or a Callout tone for an amount that carries a meaning; pair the tone with a sign or a word.secondaryis a muted second line under a cell. - Totals and subtotals are
TableFootrows, never body rows. ATableFootrow takes notone. - Mark a row's status with
tone(a stripe and a soft fill, ormutedto fade the row) or withstripe(the stripe alone, in theStackedBartones, which keeps the row's hover). Give one of them, never both, and name the status in a cell as well (aPill), or draw aLegendfor the colours. - Group rows with a
TableGroupRowand give itcolSpanequal to the table's column count;trailingis a count or a subtotal at the far end. A full-width note or empty state is aTableCellwithcolSpan. - A column with nothing to say in its head (its actions) takes a
TableHeaderCellwithsrLabel="Actions".variant="plain"drops the card, for a table inside aCard. - A prose cell wraps; a
numericormonocell never breaks.truncatecuts a long value to one line and keeps its full text as atitle; its content is plain text. - To make a whole row clickable, put one router
<Link>withrowLinkClassin its first cell. Keep an action that is not navigation aButtonin an actions column.
Accessibility
- A Table renders a native
<table>;captionis its accessible name, visually hidden. - A
TableHeaderCellis a<th scope="col">; aTableGroupRowis a<th scope="rowgroup">. srLabelis hidden text inside the header cell, so an actions column still has a name for a screen reader.- Colour alone never carries a row's or a cell's status; name it in text.
- A
truncatecell puts its full text in atitle. - A
rowLinkClasslink is a real link and the only tab stop in its row; the row's whole area is its click target. - On a narrow screen the card scrolls sideways instead of squeezing its columns.
API
Table
A list of records in a rounded, bordered card that scrolls sideways on a narrow screen. Compose it from TableHead, TableBody, TableFoot, TableRow, TableHeaderCell and TableCell. caption names the table for assistive tech and is not drawn. variant="plain" drops the card (its border, fill and shadow), the head's tinted strip and the row hover, and keeps the row dividers; it is for a table that sits inside a card of its own. Other props (id, data-*, aria-*) go on the <table>.
caption- Type
string
variant- Type
"card" | "plain"- Default
card
TableHead
The <thead>: small uppercase column titles, on a tinted strip unless the table is plain. Holds one TableRow of TableHeaderCells.
TableBody
The <tbody>: the rows, each with a line above it and, unless the table is plain, a fill on hover.
TableFoot
The <tfoot>: totals and subtotals. Its cells are strong by default and each row has a firmer line above it, in a card and in a plain table alike. Put it after TableBody; a row of it takes no tone.
TableRow
A <tr>, relative: the anchor a rowLinkClass link stretches to. Use it in the head and the body. In the body it also draws the line above the row and, unless the table is plain, the hover fill. tone marks the row's status (see RowTone): a stripe on the leading edge and a soft fill, or muted to fade it. A toned row keeps its fill on hover (the status stays readable), so a linked row's link, not a hover fill, says it is clickable. stripe is the stripe alone, in the TextTone colours (the names a StackedBar segment takes), with no fill: the row keeps its hover and its own look. Give tone or stripe, not both. Body rows only; colour alone does not say it, so name the status in a cell too (a Pill). A bold subtotal is not a tone: it is a TableFoot row.
stripe- Type
"positive" | "danger" | "info" | "warn" | "muted"
tone- Type
"positive" | "danger" | "neutral" | "info" | "warn" | "muted"
TableHeaderCell
A column title, <th scope="col">. A column with nothing to show in its head (a row's actions) takes srLabel instead of children: the name a screen reader hears ("Actions"), visually hidden so the head keeps its column.
align- Type
"end" | "start"- Default
start
srLabel- Type
string
TableCell
A <td>. tone is the text weight and colour (see CellTone; in a TableFoot it defaults to strong); numeric sets tabular figures, mono a small mono face. secondary is a second, muted line under the content. One rule for wrapping: prose wraps onto more lines (never narrower than min-w-48, so it scrolls the table before it becomes a sliver), and a numeric or mono cell (a figure, a code, a type) never breaks. truncate caps a long value's width, cuts it with an ellipsis on one line and puts the full text in a title; the content is then plain text (strings and numbers), since that is what the title shows. colSpan passes through: a cell that spans the row is a full-width panel (an empty state, a note under a group).
align- Type
"end" | "start"- Default
start
mono- Type
boolean- Default
false
numeric- Type
boolean- Default
false
secondary- Type
ReactNode
tone- Type
"strong" | "default" | "positive" | "danger" | "neutral" | "info" | "warn" | "muted"
truncate- Type
boolean- Default
false
TableGroupRow
A full-width group header in the body: a tinted row with a label that spans the table's colSpan columns (its real column count), and optional trailing content at the far end (a count, a subtotal, a small button). The group's rows follow it. For a panel of other content across the whole row, use a TableCell with colSpan instead.
colSpan required- Type
number
label required- Type
ReactNode
trailing- Type
ReactNode
Functions
rowLinkClassThe one link in a row that stretches over the whole row, for a router's own<Link>in the row's first cell; a plain link is aTextLink.