Rime

Table

The one table look: neutral header row, whole-cell sortable headers, skeleton, empty and error states, and Pagination.

Table is presentational: you own the rows, the sort state and the page, and it draws them. Columns declare a header, a cell renderer, alignment, width, a monospace flag for references, a skeleton width for loading, and hideBelow to drop a column on narrow screens.

A sortable header is the whole cell, edge to edge: the button fills the th, so hover, press and focus light the full cell over the neutral sweep, never a pill inside it. The sort chevron always holds its slot, so labels never shift when sorting.

Every state is built in: loading draws skeleton rows matching the columns; empty renders whatever you pass (an EmptyState compact that says what to do next); error replaces the body (an Alert with a retry). Pagination sits centred under the table with a summary line.

Two appearances: glass (rows fall through to the frosted panel, the default inside a GlassPanel) and white (an inset solid table for dense lists). Both share one header look, set by --table-* tokens.

Installation

npx shadcn@latest add KezLahd/rime/table

Also installs: base, icon, skeleton.

Usage

import { Pagination, Table, type Column, type SortState } from "@/components/ui";

const columns: Column<Invoice>[] = [
  { key: "ref", header: "Invoice", cell: (i) => i.reference, mono: true },
  { key: "customer", header: "Customer", sortable: true, cell: (i) => i.customer },
];

<Table caption="Invoices" columns={columns} rows={rows} rowKey={(i) => i.id} sort={sort} onSortChange={setSort} />
<Pagination page={page} pageCount={pageCount} onPageChange={setPage} totalItems={total} pageSize={20} itemLabel="invoices" />

Examples

Sortable, with pagination

Invoices
InvoiceActions
INV-1042Jane Cooper29 Sep 2026$1,280PaidView
INV-1041Wade Warren27 Sep 2026$640SentView
INV-1040Esther Howard24 Sep 2026$2,150OverdueView
INV-1039Cameron Williamson22 Sep 2026$395PaidView

Loading, empty and error

Switch the state to see each. Empty tells the user what to do next.
Invoices (loading)
InvoiceCustomerIssuedAmountStatusActions

White appearance

Invoices
InvoiceCustomerIssuedAmountStatus
INV-1042Jane Cooper29 Sep 2026$1,280Paid
INV-1041Wade Warren27 Sep 2026$640Sent
INV-1040Esther Howard24 Sep 2026$2,150Overdue

API

Generated from the component's TypeScript types and JSDoc (scripts/gen-ui-registry.mjs), so it always matches the source.

Table

Table props
PropTypeDefaultDescription
columnsRequiredReadonlyArray<Column<T>>
rowsRequiredReadonlyArray<T>
rowKeyRequired(row: T) => string
captionRequiredstringScreen-reader caption. Required: a table needs a name.
sortSortStatenull
onSortChange(sort: SortState) => voidHeader click: asc first, then toggles. Sort the rows yourself.
loadingboolean
skeletonRowsnumber5
emptyReactNodeShown when not loading and rows is empty. Pass an <EmptyState compact />.
errorReactNodeReplaces the body when the load failed. Pass an <Alert> with a retry.
appearance"glass" | "white""glass"glass = rows fall through to the frosted panel (no wrapper background). white = the reference white inset table, for dense lists.
isRowHighlighted(row: T) => booleanMarks a row, e.g. the one just edited.
classNamestring

Pagination

Pagination props
PropTypeDefaultDescription
pageRequirednumber
pageCountRequirednumber
onPageChangeRequired(page: number) => void
totalItemsnumber
pageSizenumber
itemLabelstring"results"Plural noun for the summary: "customers", "requests".
classNamestring

Theme tokens

Per-component tokens are undeclared by default, so the default shown is the fallback the CSS uses. Set one at :root (or in theme.css) to restyle every instance. Live values are read from this page, so they follow the Rime Studio.

Table per-component tokens
TokenDefaultLive value
--button-primary-bgvar(--brand-gradient)…
--button-radiusvar(--r-sm)…
--inner-radiusvar(--r-lg)…
--r-flatvar(--table-radius, var(--r-md)) / var(--table-radius, var(--inner-radius, var(--r-lg)))…
--table-radiusvar(--r-md) / var(--inner-radius, var(--r-lg))…
--table-row-height46px…

System tokens it reads

Table system tokens
TokenDefaultLive value
--brand-deeptokens.css…
--brand-gradienttokens.css…
--control-h-smtokens.css…
--durtokens.css…
--easetokens.css…
--focus-colortokens.css…
--focus-ringtokens.css…
--font-monotokens.css…
--glass-fill-nestedtokens.css…
--glow-smtokens.css…
--ink-bodytokens.css…
--ink-brandtokens.css…
--ink-disabledtokens.css…
--ink-inversetokens.css…
--ink-mutedtokens.css…
--line-glasstokens.css…
--line-softtokens.css…
--r-lgtokens.css…
--r-mdtokens.css…
--r-smtokens.css…
--rgb-contacttokens.css…
--surface-solidtokens.css…
--table-cell-padtokens.css…
--table-head-filltokens.css…
--table-head-hovertokens.css…
--table-head-inktokens.css…
--table-head-padtokens.css…
--table-head-presstokens.css…
--table-head-ruletokens.css…
--table-row-hovertokens.css…
--table-row-linetokens.css…
--text-captiontokens.css…
--text-smalltokens.css…
--wash-subtletokens.css…

Accessibility

  • caption is required and rendered as a visually hidden <caption>: a table needs a name.
  • Sortable headers are buttons inside the th with aria-sort on the th; the first press sorts ascending, then toggles.
  • Rows are not clickable. Put a link (or a ghost Button with href) in the identifying cell, so keyboard and screen-reader users reach it.
  • Pagination is a nav labelled "Pagination", the current page is aria-current="page", and prev/next disable at the ends.

Do and don't

Do

  • Put the table, its FilterBar and its Pagination in one GlassPanel: that is the list page pattern.
  • Right-align numbers and actions (align="right").
  • Give every empty state a next step.

Don't

  • Never invent another table look; theme --table-* tokens instead.
  • Don't uppercase column headers; sentence case only.
  • Don't put a title or an "n of m shown" line above the table.

For AI agents

This page as markdown, rendered from the same registry entry. Point an agent at it (or add .md to this page's URL), or at /llms.txt for the index.

Open table.md

Related: filter-bar, empty-state, glass-panel