Styled semantic table primitives for small and static data — invoice lines, settings lists, chart data. Row height and cell padding follow the density axis (--row-h, --cell-px); numbers align to the end with tabular figures; the header and first column can stick without ever blurring.
| Item | Qty | Rate | Amount |
|---|---|---|---|
Brand identity refresh Logo, colour system, type scale | 1 | $4,500.00 | $4,500.00 |
Website build — 8 templates Next.js, CMS integration | 8 | $650.00 | $5,200.00 |
Copywriting Home, services, about | 3 | $180.00 | $540.00 |
Hosting & maintenance Nov 2026 – Oct 2027 | 12 | $40.00 | $480.00 |
| TotalUnpaid | $10,720.00 | ||
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { Table, TableBody, TableCaption, TableCell, TableContainer, TableHead, TableHeader, TableRow } from "@vamos-ui/react";
<TableContainer aria-label="Invoice lines">
<Table>
<TableCaption>Invoice INV-2041</TableCaption>
<TableHeader>
<TableRow><TableHead>Item</TableHead><TableHead numeric>Amount</TableHead></TableRow>
</TableHeader>
<TableBody>
<TableRow><TableCell>Design retainer</TableCell><TableCell numeric>$4,500.00</TableCell></TableRow>
</TableBody>
</Table>
</TableContainer>For sorting, filtering, selection and pagination at scale use Data Table — it renders with these primitives.
Glass: sticky header and pinned column use color-mix(var(--surface), var(--scene)) — opaque, so rows never show through, and no extra blur layer.
Examples
Density
compact / comfortable / spacious set --row-h and --cell-px on the table; "inherit" (default) follows the page data-density.
| compact | Role | Team | Since |
|---|---|---|---|
| Ece Demir | Product designer | Studio | Mar 2023 |
| Meltem Aydın | Account manager | Clients | Jan 2022 |
| Can Özkan | Frontend engineer | Platform | Sep 2024 |
| comfortable | Role | Team | Since |
|---|---|---|---|
| Ece Demir | Product designer | Studio | Mar 2023 |
| Meltem Aydın | Account manager | Clients | Jan 2022 |
| Can Özkan | Frontend engineer | Platform | Sep 2024 |
| spacious | Role | Team | Since |
|---|---|---|---|
| Ece Demir | Product designer | Studio | Mar 2023 |
| Meltem Aydın | Account manager | Clients | Jan 2022 |
| Can Özkan | Frontend engineer | Platform | Sep 2024 |
Sticky header and first column
Give TableContainer a maxHeight. Sticky cells use an opaque surface mix (never blur); the edges fade while there is more to scroll.
| Page | Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| / | 1,480 | 2,136 | 2,792 | 1,931 | 2,587 | 1,726 | 2,382 | 1,521 | 2,177 | 2,833 | 1,972 | 2,628 |
| /menu | 2,333 | 1,472 | 2,128 | 2,784 | 1,923 | 2,579 | 1,718 | 2,374 | 1,513 | 2,169 | 2,825 | 1,964 |
| /reservations | 1,669 | 2,325 | 1,464 | 2,120 | 2,776 | 1,915 | 2,571 | 1,710 | 2,366 | 1,505 | 2,161 | 1,300 |
| /blog/qr-menus-guide | 2,522 | 1,661 | 2,317 | 1,456 | 2,112 | 1,251 | 1,907 | 2,563 | 1,702 | 2,358 | 1,497 | 2,153 |
| /blog/local-seo-2026 | 1,858 | 2,514 | 1,653 | 2,309 | 1,448 | 2,104 | 1,243 | 1,899 | 2,555 | 1,694 | 2,350 | 1,489 |
| /contact | 1,194 | 1,850 | 2,506 | 1,645 | 2,301 | 1,440 | 2,096 | 1,235 | 1,891 | 1,030 | 1,686 | 2,342 |
| /about | 2,047 | 1,186 | 1,842 | 981 | 1,637 | 2,293 | 1,432 | 2,088 | 1,227 | 1,883 | 1,022 | 1,678 |
| /events | 1,383 | 2,039 | 1,178 | 1,834 | 973 | 1,629 | 2,285 | 1,424 | 2,080 | 1,219 | 1,875 | 1,014 |
| /gift-cards | 2,236 | 1,375 | 2,031 | 1,170 | 1,826 | 965 | 1,621 | 760 | 1,416 | 2,072 | 1,211 | 1,867 |
| /careers | 1,572 | 711 | 1,367 | 2,023 | 1,162 | 1,818 | 957 | 1,613 | 752 | 1,408 | 2,064 | 1,203 |
| /blog/nfc-vs-paper | 908 | 1,564 | 703 | 1,359 | 2,015 | 1,154 | 1,810 | 949 | 1,605 | 744 | 1,400 | 2,056 |
| /privacy | 1,761 | 900 | 1,556 | 695 | 1,351 | 490 | 1,146 | 1,802 | 941 | 1,597 | 736 | 1,392 |
Numbers and sorting (CRM)
numeric end-aligns with tabular mono figures (also in RTL). onSort turns the header into a button with aria-sort.
| Northwind Traders | 48 | $12,400.00 | Up 21.4% |
|---|---|---|---|
| Moda Wellness | 6 | $1,450.00 | Up 8.2% |
| Harbor & Pine | 4 | $980.50 | Down 3.1% |
| Kadıköy Bistro | 2 | $320.00 | No change |
Row states (HR)
highlighted (accent bar on the start edge), selected, disabled — with bordered="grid".
| Member | Role | Last active |
|---|---|---|
| Can Özkan just invited | Editor | — |
| Meltem Aydın | Admin | 2 hours ago |
| Ece Demir | Author | Yesterday |
| Ahmet Kaya | Suspended | Oct 2 |
Empty
TableEmpty spans every column and renders the kit's Empty state; children become its actions.
| Number | Client | Due | Amount |
|---|---|---|---|
API
Table
| Prop | Type | Default |
|---|---|---|
densityRow height and cell padding via --row-h / --cell-px. | "inherit" | "compact" | "comfortable" | "spacious" | "inherit" |
stripedZebra rows. | boolean | false |
hoverRow hover tint. | boolean | true |
borderedHairlines between rows, around every cell, or none. | "rows" | "grid" | "none" | "rows" |
stickyHeaderHeader stays visible while the container scrolls (opaque fill). | boolean | false |
stickyFirstColumnPins the first column (inset-inline-start, RTL-aware). | boolean | false |
layoutfixed honours the widths you set on TableHead. | "auto" | "fixed" | "auto" |
aria-labelRequired unless the table has a TableCaption (dev warning). | string | — |
TableContainer
| Prop | Type | Default |
|---|---|---|
aria-labelNames the focusable scroll region (role="region", tabIndex 0). | string | — |
maxHeightVertical scroll limit, e.g. "24rem" — needed for stickyHeader. | string | — |
TableHead
| Prop | Type | Default |
|---|---|---|
numericEnd-aligned header for a number column. | boolean | false |
alignExplicit alignment (logical). | "start" | "center" | "end" | — |
sortCurrent sort; sets aria-sort and the icon. | "asc" | "desc" | false | — |
onSortMakes the label a sort button. | () => void | — |
scopeHeader scope. | "col" | "colgroup" | "col" |
TableCell
| Prop | Type | Default |
|---|---|---|
numerictext-end, tabular, mono, no wrap. | boolean | false |
alignExplicit alignment. | "start" | "center" | "end" | — |
truncatetrue → one line with ellipsis; a number clamps to that many lines. | boolean | number | — |
rowHeaderRender as <th scope="row">. | boolean | false |
TableRow
| Prop | Type | Default |
|---|---|---|
selecteddata-state="selected" + aria-selected. | boolean | — |
disabledMuted row, aria-disabled. | boolean | — |
highlightedAccent bar at the start edge. | boolean | — |
TableCaption
| Prop | Type | Default |
|---|---|---|
visuallyHiddenCaption for screen readers only. | boolean | false |
sideCaption position. | "top" | "bottom" | "bottom" |
TableEmpty
| Prop | Type | Default |
|---|---|---|
colSpanRequired. Columns to span. | number | — |
titleMain line (dictionary table.empty). | ReactNode | "No results" |
descriptionSecondary line. | ReactNode | — |
iconReplace or hide the icon. | ReactNode | false | — |
childrenActions (e.g. Clear filters). | ReactNode | — |
TableHeader / TableBody / TableFooter
| Prop | Type | Default |
|---|---|---|
...propsthead / tbody / tfoot. | HTMLAttributes | — |