Table
A complete primitive set for tabular data — header, body, footer, sortable columns, sticky header, row states, plus cell helpers for avatars, status, currency amounts, and actions. Designed in the Wise / PrimeReact tradition.
Playground
Installation
pnpm add @tessinaui/uiUsage
import {
TableRoot,
TableHeader,
TableBody,
TableFooter,
TableRow,
TableHead,
TableCell,
TableCaption,
TableEmpty,
TableCellAvatar,
TableCellAmount,
TableCellStatus,
TableCellActions,
} from "@tessinaui/ui";<TableRoot variant="lined" hoverable>
<TableHeader>
<TableRow hoverable={false}>
<TableHead sortable sortDirection="asc" onSort={handleSort("name")}>User</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
<TableHead numeric>Amount</TableHead>
<TableHead align="end" />
</TableRow>
</TableHeader>
<TableBody>
{users.map((u) => (
<TableRow key={u.id}>
<TableCell>
<TableCellAvatar
avatar={<Avatar size="sm" initials={initialsOf(u.name)} />}
name={u.name}
secondary={u.email}
/>
</TableCell>
<TableCell>{u.role}</TableCell>
<TableCell>
<TableCellStatus intent="success" label="Active" />
</TableCell>
<TableCell numeric>
<TableCellAmount currency="$" amount={u.amount.toFixed(2)} />
</TableCell>
<TableCell align="end">
<TableCellActions>
<IconButton variant="ghost" size="xs" icon={<MoreHorizontal />} aria-label="Actions" />
</TableCellActions>
</TableCell>
</TableRow>
))}
</TableBody>
</TableRoot>Showcase
When to use
Table is the workhorse for any structured tabular data — users, transactions, orders, audit logs, settings rows, etc. It's a primitive set: bring your own data fetching, pagination, and selection state, and Tessina handles the visual layer plus sorting indicators, sticky header, and the cell patterns that show up everywhere.
For a turnkey component with built-in filtering / pagination / selection, layer TanStack Table on top of these primitives — the styling will carry through unchanged.
API Reference
TableRoot props
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Cell padding + text size |
variant | "lined" | "striped" | "bordered" | "borderless" | "lined" | Visual treatment — lined is default, striped adds zebra rows, bordered adds vertical separators, borderless removes the container chrome |
rounded | "none" | "sm" | "md" | "lg" | "full" | "md" | Container corner radius |
intent | "none" | "primary" | "error" | "warning" | "success" | "info" | "none" | Subtle border accent on the container |
dir | "ltr" | "rtl" | "ltr" | Text direction — flips alignment and sticky-column side |
hoverable | boolean | true | Highlight rows on hover |
stickyHeader | boolean | false | Stick the header to the top of the scroll container |
withContainer | boolean | true | Wrap the <table> in a TableContainer (you can disable to use your own) |
TableContainer props
A standalone scroll container — useful if you need to wrap a table you didn't build with TableRoot.
| Prop | Type | Default | Description |
|---|---|---|---|
size, variant, rounded, intent | same as Root | — | Visual chrome |
scrollable | boolean | true | Add horizontal overflow scrolling |
TableHeader / TableBody / TableFooter
Semantic <thead> / <tbody> / <tfoot> wrappers. Header gets the muted background; footer gets the same styling for totals rows.
TableRow props
| Prop | Type | Default | Description |
|---|---|---|---|
selected | boolean | false | Highlights the row in the primary tint |
disabled | boolean | false | Greys out the row and disables pointer events |
destructive | boolean | false | Tints the row in error colour (e.g. flagged for deletion) |
hoverable | boolean | inherited | Override the parent's hoverable setting for this row |
TableHead props
| Prop | Type | Default | Description |
|---|---|---|---|
align | "start" | "center" | "end" | "start" | Text alignment |
numeric | boolean | false | Right-align with tabular numerals |
sortable | boolean | false | Show a sort indicator and accept clicks to cycle |
sortDirection | "asc" | "desc" | "none" | "none" | Current sort direction (controlled) |
onSort | (next: SortDirection) => void | — | Called when the user clicks the indicator. Receives the next direction in the cycle (none → asc → desc → none) |
sticky | boolean | false | Stick this column to the start (left in LTR, right in RTL) |
width | number | string | — | Column width hint |
Sets aria-sort automatically when sortable is true.
TableCell props
Same align, numeric, sticky, width props as TableHead, plus truncate for ellipsis overflow.
TableEmpty props
| Prop | Type | Default | Description |
|---|---|---|---|
colSpan | number | required | Number of columns in the table — required so the cell spans correctly |
icon | ReactNode | — | Optional icon shown above the message |
title | ReactNode | — | Bold title above the body text |
Renders a single row with one cell that spans the table.
Cell helpers
These compose inside TableCell and cover the patterns that show up in nearly every product table.
TableCellAvatar
<TableCell>
<TableCellAvatar
avatar={<Avatar src={user.photo} initials={initialsOf(user.name)} />}
name={user.name}
secondary={user.email}
/>
</TableCell>| Prop | Description |
|---|---|
avatar | Slot for any avatar component |
name | Primary text (bold) |
secondary | Optional second line (muted) |
TableCellAmount
Right-aligns a currency amount with optional comparison delta (tinted by intent).
<TableCell numeric>
<TableCellAmount
currency="$"
amount="1,240.50"
delta="+12.4%"
deltaIntent="success"
/>
</TableCell>| Prop | Description |
|---|---|
amount | Main number (tabular numerals) |
currency | Optional symbol or code shown before |
delta | Optional comparison line below |
deltaIntent | "none" | "success" | "error" |
TableCellStatus
Dot + label, in 6 intents (neutral, primary, error, warning, success, info).
<TableCell>
<TableCellStatus intent="success" label="Active" />
</TableCell>TableCellActions
A trailing-edge container for icon-button actions.
<TableCell align="end">
<TableCellActions>
<IconButton variant="ghost" size="xs" icon={<Pencil />} aria-label="Edit" />
<IconButton variant="ghost" size="xs" icon={<Trash />} aria-label="Delete" />
</TableCellActions>
</TableCell>TableCellText
Two-line cell with primary + muted secondary.
<TableCell>
<TableCellText primary="Wire transfer" secondary="ACH · 2 days" />
</TableCell>Notes
- The
<table>element renders inside aTableContainerby default, which provides the rounded card chrome, intent border, and horizontal scroll for narrow viewports. PasswithContainer={false}if you want to nest the table inside your own container (e.g. a Card). aria-sort="ascending" | "descending" | "none"is set automatically on sortable headers.- For huge datasets, virtualize the body with TanStack Virtual or a similar library — the primitives don't impose any virtualization but compose naturally with one.
- For RTL: alignment, sortable indicators, and sticky-column side all flip via the
dirprop. Numbers and currency stay LTR-formatted (which is the typographic norm even in Arabic-script copy).
Scroll Area
A scrollable container with custom overlay scrollbars that do not steal layout space. Supports vertical, horizontal, and both-axis content with a neutral branded thumb, RTL, and a compound API for full control.
ChatBubble
AI chat message bubbles with role-aware alignment, streaming state, action buttons, and RTL support.