Kanzo UI
Data display

Table

The presentational table primitive — semantic elements, tokenised chrome, no data layer.

Ark UI

Table vs Data Table: this one is markup only — semantic elements with tokenised chrome and no data layer. You bring the rows and render them.

DataTableRoot is this table plus TanStack Table: sorting, filtering and pagination. It costs a dependency, so it lives on its own entry point (@kanzo-tech/ui/table) with the peer marked optional — importing it never affects consumers who only want the markup.

This pair is a rule, not a one-off — see the engine rule. A component that needs an engine ships as two: the presentational half in the root barrel, the connected half on the engine's subpath, rendering the first. StatTile (a figure you pass) and ChartStat (a figure it queries, reacting to the crossfilter) are the same pair over Mosaic. It is not duplication: the connected half renders the presentational one. It is what keeps showing a number from a REST call free of DuckDB-WASM.

Contracts the Amber Hall has on the board.
ContractRegionState
Something is eating the bell-ropesThornmarchOpen
A wyrm under the granaryThornmarchFailed
Nine goats, one roadThornmarchSettled
The miller's second cellarThornmarchSettled

Usage

import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from "@kanzo-tech/ui";
<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Contract</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Nine goats, one road</TableCell>
    </TableRow>
  </TableBody>
</Table>

This is the markup layer only. For sorting, filtering and pagination, use DataTable — it composes these same parts over TanStack Table on a separate entry point.

Anatomy

<Table>
  <TableCaption />
  <TableHeader>
    <TableRow>
      <TableHead />
  <TableBody>
    <TableRow>
      <TableCell />
  <TableFooter>
    <TableRow>
      <TableCell />

Table renders its own overflow-auto wrapper, so a wide table scrolls inside itself rather than pushing the page sideways.

Striped

variant and isHoverable land as data-* attributes on the <table>; the rows read them through a group selector, so no row has to be told which variant it is in.

RoleDuty
WardenHolds the line and signs for the party
ScoutWalks it first, alone, and comes back
CantorKeeps the wards lit and the dead quiet
SapperOpens doors that were not doors
AlchemistCarries what must not be dropped
ArchivistReads the contract before anyone signs it

Two parts sit at the bottom of a table and are easy to mix up:

  • TableFooter is a <tfoot> — a row of the table, for a summary the columns add up to: a total, a count. It lines up with the columns above it (right-align a figure, colSpan a label), and reads as the emphasised last row.
  • TableCaption is a <caption> — a label for the whole table, not a row and not tied to the columns. It names what the table is ("Contracts the Amber Hall has on the board") and, via caption-bottom, renders beneath it.

So a bottom row of totals is the footer; a sentence describing the table is the caption. If you reached for a footer and got descriptive text, you wanted the caption — and the other way round.

HallPosted
Amber Hall200 gold
Salt334 gold
The Nine329 gold
Ash Co.704 gold
Lanternwood397 gold
On the board1,964 gold

API Reference

Table

PropTypeDefault
variant"plain" | "striped""plain"
isHoverablebooleantrue

Every part extends the matching ark.* element (ark.table, ark.thead, ark.tr, …), so ref and all native table attributes — colSpan, scope, align — pass through.

TableBodyProps is exported, so a wrapper can take the same props without restating them.

On this page