Table
Shows data compared across rows and columns.
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
<Table label="Orders">
<TableCaption>Orders, October 2026</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Order</TableHead>
<TableHead>Customer</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{orders.map((order) => (
<TableRow key={order.id}>
<TableCell>{order.id}</TableCell>
<TableCell>{order.customer}</TableCell>
<TableCell className="text-right font-mono tabular-nums">{order.amount}</TableCell>
</TableRow>
))}
</TableBody>
</Table>Examples
Example source
var P = window.PUI, h = React.createElement;
var rows = [['#4821', 'Maya Chen', 'success', '✓ Paid', '$1,280.00'], ['#4822', 'Leo Park', 'warning', '! Pending', '$349.90'], ['#4823', 'Sara Diaz', 'destructive', '× Declined', '$2,015.50']];
ReactDOM.createRoot(document.getElementById('preview')).render(h(P.Table, null,
h(P.TableCaption, null, 'Orders, October 2026'),
h(P.TableHeader, null, h(P.TableRow, null, h(P.TableHead, null, 'Order'), h(P.TableHead, null, 'Customer'), h(P.TableHead, null, 'Status'), h(P.TableHead, { className: 'num' }, 'Amount'))),
h(P.TableBody, null, rows.map(function (r) { return h(P.TableRow, { key: r[0] },
h(P.TableCell, { style: { fontWeight: 500 } }, r[0]), h(P.TableCell, null, r[1]), h(P.TableCell, null, h(P.Badge, { variant: r[2] }, r[3])), h(P.TableCell, { className: 'num' }, r[4])); }))));
Usage
What you provide
TableHeaderwith shortTableHeadlabels;TableBodywith rows; optionalTableCaption.- Numbers and amounts: right-aligned,
font-mono tabular-nums, en-US format ($1,280.00,Oct 21, 2026). - Status as a
Badge. - Empty state: one row with
colSpanand a sentence saying what will appear and how to add the first item.
Accessibility
- Use tables for data compared by rows and columns, never for layout.
- Always give it a
TableCaption(or a heading) that explains what the data is. Column headers are<th scope="col">; for row headers use<th scope="row">. - Static cells take no focus. When the table scrolls sideways, PUI makes the container focusable so arrows can scroll it; pass
labelto name that region. - Sortable columns use a button in the header with the current direction exposed (
aria-sorton theth); focus stays on the control after sorting. - Row selection shows single, partial and all states; say how many records a bulk action affects before running it.
- Design loading, empty, no-match and failed states as different messages.
Adapted from Magoo 1.6.0 by Marcelo Sales: Tables (tabelas). Magoo is educational guidance; test the behavior in your implementation.
API
Install with:
npx @aline-aroucha/pui add table badge
<Table>
<TableCaption />
<TableHeader>
<TableRow>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell />
</TableRow>
</TableBody>
<TableFooter />
</Table>
Table
| Name | Type |
|---|---|
label | string |
| Names the scrolling region for screen readers when the table scrolls sideways. | |
TableCaption
| Name | Type |
|---|---|
children | ReactNode |
| What the data is. Always include one. | |
TableHead
| Name | Type | Default |
|---|---|---|
scope | "col" | "row" | "col" |
| Which cells this header describes. | ||
TableRow
| Name | Type |
|---|---|
data-state | "selected" |
| Highlights a selected row. | |