Skip to content

Table

One table for activity, UTXOs and explorer data. Rows are virtualized against a fixed row height, amounts are always mono and right-aligned, and every async posture — first load, next page, empty, failed — has an explicit rendering.

Density

Comfortable 48px rows for wallet activity; dense 36px rows for the explorer. Headers are click-to-sort (live) — both ship sorted newest first via defaultSort.

Comfortable — rowHeight 48

12 fixture transactions: send/receive/swap × pending/confirming/confirmed/failed.

Dense — rowHeight 36
import { Table, type TableColumn } from "@coldharbor/ui";
import { transactions, type Transaction } from "@/fixtures";

const columns: TableColumn<Transaction>[] = [
  { key: "timestamp", header: "Age", sortable: true },
  { key: "amountDisplay", header: "Amount", align: "right",
    cell: (tx) => <span className="font-mono tabular-nums">{tx.amountDisplay}</span> },
];

<Table
  data={transactions}
  columns={columns}
  getRowId={(tx) => tx.id}
  defaultSort={{ key: "timestamp", direction: "desc" }}
  rowHeight={48}
  height={340}
/>
  • --ch-bg
  • --ch-bg-raised
  • --ch-surface-raised
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-primary

Selection

selectable adds a leading checkbox column with select-all in the header; selected rows tint with the primary. Two rows ship pre-selected here. Row hover tint is a live pointer state — sweep the rows.

Selectable — 2 of 5 selected
<Table selectable defaultSelectedRowIds={ids} onSelectionChange={setIds} … />
  • --ch-bg
  • --ch-bg-raised
  • --ch-surface-raised
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-primary

Async postures

First load shows a full-viewport skeleton; paging appends skeleton rows under real data; empty and error render through the emptyState slot. Table has no dedicated error prop — the failed-fetch message goes through emptyState, styled with the danger token.

Loading — initial
Loading — next page
Empty
No transactions yet — receive your first coins to see them here.
Error (via emptyState)
Could not reach the node — check your connection and retry.
<Table data={[]} loading … />                       // initial skeleton
<Table data={rows} loading skeletonRows={3} … />    // paging skeleton
<Table data={[]} emptyState={<Empty />} … />        // empty
<Table data={[]} emptyState={<FetchError />} … />   // error posture
  • --ch-bg
  • --ch-bg-raised
  • --ch-surface-raised
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-primary

Resizable & reorderable headers (live)

Drag a header to reorder its column; drag the right edge of a header to resize. Both are opt-in flags.

resizable + reorderable
<Table resizable reorderable minColumnWidth={64} … />
  • --ch-bg
  • --ch-bg-raised
  • --ch-surface-raised
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-primary

Themes

A compact activity table under all three layers.

  • --ch-bg
  • --ch-bg-raised
  • --ch-surface-raised
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-primary