Table
Rows of records with sortable headers.
import { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption } from "@flinbu/seed";Sortable, with a selected row. On a phone each row is a card
| Light | Uses | Status | |
|---|---|---|---|
| bg-accent-solid | #2563EB | 14 | Published |
| border-focus | #2563EB | 0 | Draft |
| text-secondary | #525252 | 52 | Published |
import { Badge, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@flinbu/seed";
const [dir, setDir] = React.useState<"ascending" | "descending">("ascending");
const rows = [...ROWS].sort((a, b) => (dir === "ascending" ? 1 : -1) * a.name.localeCompare(b.name));
<Table frameClassName="max-w-xl">
<TableCaption>3 of 446 tokens</TableCaption>
<TableHeader>
<TableRow>
<TableHead sort={dir} onSort={() => setDir(dir === "ascending" ? "descending" : "ascending")}>Token</TableHead>
<TableHead>Light</TableHead>
<TableHead align="right">Uses</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((r) => (
<TableRow key={r.name} selected={r.name === "text-secondary"}>
<TableCell className="font-mono">{r.name}</TableCell>
<TableCell className="font-mono text-secondary">{r.value}</TableCell>
<TableCell align="right">{r.uses}</TableCell>
<TableCell><Badge variant={r.status === "Draft" ? "warning" : "success"} dot>{r.status}</Badge></TableCell>
</TableRow>
))}
</TableBody>
</Table>Stays a table on phones
| Token | Value |
|---|---|
| space-xs | 8px |
| space-sm | 12px |
| space-base | 16px |
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@flinbu/seed";
<Table stackOnMobile={false} frameClassName="max-w-xs" aria-label="Spacing">
<TableHeader><TableRow><TableHead>Token</TableHead><TableHead align="right">Value</TableHead></TableRow></TableHeader>
<TableBody>
{[["space-xs", "8px"], ["space-sm", "12px"], ["space-base", "16px"]].map(([t, v]) => (
<TableRow key={t}><TableCell className="font-mono">{t}</TableCell><TableCell align="right" className="font-mono text-secondary">{v}</TableCell></TableRow>
))}
</TableBody>
</Table>Use it for
- Lists people compare or scan: tokens, members, versions.
Use something else for
- Layout: use grids.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
density | 'comfortable''compact' | 'comfortable' | 48 or 40 px rows. |
stackOnMobile | boolean | true | Below 768px each row is a card: the first cell as its title, every other cell under its header. false keeps the table, scrolling sideways in its frame. |
frameClassName | string | – | Classes for the frame. |
TableCell: label | string | – | The header shown above the value on phones. Default the column header's text. |
TableRow: selected | boolean | – | Neutral selection color. |
TableHead: sort | 'ascending''descending'false | – | Makes the header a sort button. |
TableHead: onSort | () => void | – | Called on click. |
TableHead / TableCell: align | 'left''center''right' | 'left' | Right-align numbers. |
Accessibility
- Real table markup with scope on headers.
- Sortable headers are buttons and report aria-sort.
- Below 768px rows stack into cards and keep table roles, so each value is still read with its header; the visible label above it is hidden from screen readers so it isn't read twice.