Table
A semantic tabular base with consistent alignment and density; sorting, filtering and pagination belong to your data layer.
| Project | Status | Budget |
|---|---|---|
| Redesign 2026 | On track | $12,400 |
| Mobile app | At risk | $18,250 |
| Docs migration | Paused | $1,000 |
Installation
Run it from the root of a project already set up with zunoui init.
npx zunoui@latest add tableDependencies
What add table installs alongside the component. The CLI skips any you already have in a compatible version.
ZUNO registry
@zuno/utils
Examples
4 examples · 1 component| Project | Status | Budget |
|---|---|---|
| Redesign 2026 | On track | $12,400 |
| Mobile app | At risk | $18,250 |
| Docs migration | Paused | $1,000 |
| Total | $31,650 | |
Caption and footer
Numeric columns end-aligned with tabular figures; the caption names the table.
| Event | User | Time |
|---|---|---|
| Project created | Ana | 09:12 |
| Member invited | Luis | 09:30 |
| Budget updated | Ana | 10:05 |
| File uploaded | Marta | 10:41 |
Compact density
density="compact" for dense data, inside a bordered wrapper.
| Project | |
|---|---|
| Redesign 2026 | |
| Mobile app | |
| Docs migration |
Row selection
Selection state lives in your code; data-state marks the row and each checkbox has a name.
| Project | Owner | Budget |
|---|---|---|
No matching projectsTry a different search or clear the filters. | ||
No results
One cell spanning every column holds an Empty state.
API reference
Forwards props and ref to the container element; it only adds composition utilities.
| Prop | Type | Default |
|---|---|---|
densityRow height; compact for data-heavy contexts. | "comfortable" | "compact" | "comfortable" |
containerPropsProps for the scroll wrapper: className (border, radius, max height), and tabIndex, role and aria-label for keyboard scrolling. | ComponentProps<"div"> | — |
TableNative table inside a horizontally scrollable wrapper. | table | — |
TableHeader / TableBody / TableFooterNative row groups. | thead / tbody / tfoot | — |
TableRowRow with hover; data-state="selected" highlights it. | tr | — |
TableHeadHeader cell; add scope="row" for row headers. | th | — |
TableCellData cell. | td | — |
TableCaptionAccessible table name, shown below it. | caption | — |
States
The states that apply to this component and their visual treatment.
| State | Treatment |
|---|---|
| Hover | Rows get a subtle muted background. |
| Selected | data-state="selected" keeps the row highlighted; the checkbox conveys it without color. |
| Compact | density="compact" reduces row height for dense data. |
| Empty | A single cell spanning every column holds an Empty state. |
| Overflow | The wrapper scrolls horizontally when the columns do not fit. |
Accessibility
Native table semantics: screen readers announce rows, columns and headers without extra roles.
Give the table a name with TableCaption (or aria-label / aria-labelledby when the caption would be redundant).
Mark selection with data-state="selected" plus a checkbox with an accessible name; the highlight is not the only indicator.
The wrapper scrolls horizontally on narrow screens instead of breaking the layout. When a table can overflow and has no focusable content, pass containerProps={{ tabIndex: 0, role: "region", "aria-label": … }} so keyboard users can scroll it (Safari does not make scrollers focusable).
Headers and cells use logical alignment (text-start), so the table reads correctly in right-to-left layouts; end-align numeric columns and use tabular-nums.