Table
In the appThe data table as the app draws it: soft grid lines between columns, a fill-soft header, hover rows, and a selected row in the accent wash.
@varick/ui/components/tableAdded 2 Sept 2026
Agents 4
1 selected
| Success | Status | ||||
|---|---|---|---|---|---|
| Submission router | 342 | 94.2% | Running | ||
| Renewal manager | 156 | 88.1% | Review | ||
| Compliance check | 61 | 99.0% | Queued | ||
| Invoice matcher | 12 | 71.4% | Failed |
Usage
import {
Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
} from "@varick/ui/components/table";
export function Example() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead sortable sorted="asc">Agent</TableHead>
<TableHead className="text-right">Runs</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((r) => (
<TableRow key={r.id} selected={selected.has(r.id)}>
<TableCell strong>{r.name}</TableCell>
<TableCell align="right">{r.runs}</TableCell>
<TableCell><Status tone={r.tone}>{r.status}</Status></TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}API reference
Every component also accepts className and the native props of its root element.
| Prop | Type | Default | Description |
|---|---|---|---|
| TableRow.selected | boolean | false | Accent wash and data-state="selected". |
| TableHead.sortable | boolean | false | Renders the label as a button with a sort glyph. |
| TableHead.sorted | 'asc' | 'desc' | undefined | Sets the glyph and aria-sort. |
| TableCell.strong | boolean | false | Ink and medium weight, for the row’s name. |
| TableCell.align | 'left' | 'right' | 'left' | Right also sets tabular numerals. |
Accessibility
- A real <table>: header cells are <th>, sorted columns carry aria-sort.
- The wrapper scrolls horizontally so narrow screens never squash the columns.
- Selection checkboxes need aria-label with the row name, and a "Select all" in the header.
Styles
The CSS as it appears in the app. The live component above is the same values as Tailwind classes.
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th {
text-align: left;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--fg-mid);
padding: 9px 16px;
border-bottom: 1px solid var(--hairline-soft);
border-right: 1px solid var(--hairline-soft);
white-space: nowrap;
background: var(--fill-soft);
}
tbody td {
padding: 10px 16px;
border-bottom: 1px solid var(--hairline-soft);
border-right: 1px solid var(--hairline-soft);
color: var(--fg-mid);
}
thead th:last-child, tbody td:last-child { border-right: none; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--fill-soft); }
tbody tr:has(.tbl-check:checked) { background: var(--accent-fade); }
td.strong { color: var(--fg-high); font-weight: 500; }
.th-sort { display: inline-flex; align-items: center; gap: 3px; }