Table
betaA data table built from native table elements, with a caption, row sizes, and a container that scrolls sideways on narrow screens.
Preview
caption, header, body, footer
| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV-001 | Credit card | $250.00 | |
| INV-002 | Bank transfer | $150.00 | |
| INV-003 | Credit card | $350.00 | |
| INV-004 | PayPal | $450.00 | |
| Total | $1,200.00 | ||
wide, scrolls when narrow
| Name | Role | Team | Joined | Last active | |
|---|---|---|---|---|---|
| Ada Lovelace | ada@example.com | Owner | Platform | Mar 2, 2024 | 2 minutes ago |
| Grace Hopper | grace@example.com | Admin | Compilers | Jan 18, 2025 | 1 hour ago |
| Alan Turing | alan@example.com | Member | Research | Jul 9, 2025 | Yesterday |
size sm
| Plan | Seats |
|---|---|
| Free | 3 |
| Pro | 25 |
size md
| Plan | Seats |
|---|---|
| Free | 3 |
| Pro | 25 |
size lg
| Plan | Seats |
|---|---|
| Free | 3 |
| Pro | 25 |
no rows
| Order | Customer | Total |
|---|---|---|
| No orders yet. | ||
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/table
Use it for
- Showing records people compare across columns: invoices, members, orders, results.
- Name every table with a TableCaption, as the first part inside Table.
- Put the column names in a TableHeader row of TableHead cells. Use TableHead with scope="row" for a cell that names its row.
- Pass scrollLabel when the table can be wider than its container, like on a phone. It makes the container a named region keyboard users can scroll.
- Use size to set row height: sm for dense data, md by default, lg for short tables with room to breathe.
- Right-align numbers with className="text-right" on their TableHead and TableCell, so digits line up.
- For no rows, render one row with a single TableCell whose colSpan covers every column, and say why it is empty.
Avoid it for
- Laying out a page or a form. Use grid or flex. A table tells screen readers the content is data.
- One column of items. Use a list.
- Sorting, filtering, selecting rows, or editing cells. This table only displays. Add those with real buttons and inputs in the cells.
- Very long text in a cell. Cells do not wrap, so a paragraph makes the table very wide. Pass className="whitespace-normal" to that cell.
Props
- size
On Table. Row height and cell padding: a one-line row is as tall as a control of the same size. Density scales it.
"sm" | "md" | "lg" · default "md"
- scrollLabel
On Table. Makes the scroll container a focusable region with this name, so keyboard users can scroll a wide table. Use the caption's words.
string
- Table
A <table> inside a container that scrolls sideways. className and other props go on the <table>.
Omit<ComponentProps<"table">, "color"> & { size?, scrollLabel? }
- TableCaption
Part. The table's name. Put it first inside Table. It shows below the table, left-aligned.
Omit<ComponentProps<"caption">, "color">
- TableHeader
Part. The header row group. Its rows take no hover fill.
Omit<ComponentProps<"thead">, "color">
- TableBody
Part. The body row group.
Omit<ComponentProps<"tbody">, "color">
- TableFooter
Part. A footer row group for totals, on a muted fill.
Omit<ComponentProps<"tfoot">, "color">
- TableRow
Part. One row, with a bottom border and a muted fill on hover.
Omit<ComponentProps<"tr">, "color">
- TableHead
Part. A header cell, in muted-foreground. Does not wrap.
Omit<ComponentProps<"th">, "color">
- TableCell
Part. A data cell. Does not wrap. Pass className="whitespace-normal" to let it wrap.
Omit<ComponentProps<"td">, "color">
- className
Extra classes on any part. Later classes win, so className="py-4" on a cell replaces its padding.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | On Table. Row height and cell padding: a one-line row is as tall as a control of the same size. Density scales it. |
| scrollLabel | string | - | On Table. Makes the scroll container a focusable region with this name, so keyboard users can scroll a wide table. Use the caption's words. |
| Table | Omit<ComponentProps<"table">, "color"> & { size?, scrollLabel? } | - | A <table> inside a container that scrolls sideways. className and other props go on the <table>. |
| TableCaption | Omit<ComponentProps<"caption">, "color"> | - | Part. The table's name. Put it first inside Table. It shows below the table, left-aligned. |
| TableHeader | Omit<ComponentProps<"thead">, "color"> | - | Part. The header row group. Its rows take no hover fill. |
| TableBody | Omit<ComponentProps<"tbody">, "color"> | - | Part. The body row group. |
| TableFooter | Omit<ComponentProps<"tfoot">, "color"> | - | Part. A footer row group for totals, on a muted fill. |
| TableRow | Omit<ComponentProps<"tr">, "color"> | - | Part. One row, with a bottom border and a muted fill on hover. |
| TableHead | Omit<ComponentProps<"th">, "color"> | - | Part. A header cell, in muted-foreground. Does not wrap. |
| TableCell | Omit<ComponentProps<"td">, "color"> | - | Part. A data cell. Does not wrap. Pass className="whitespace-normal" to let it wrap. |
| className | string | - | Extra classes on any part. Later classes win, so className="py-4" on a cell replaces its padding. |
Accessibility
- Every part is the native element: <table>, <caption>, <thead>, <tbody>, <tfoot>, <tr>, <th>, and <td>. Screen readers can move by row and column and read each cell's header.
- TableCaption names the table. Screen readers read it when they reach the table.
- A <caption> must be the first child of its <table>, so put TableCaption first.
- scrollLabel makes the container a role="region" with that name and tabIndex=0, so keyboard users can Tab to it and scroll with the arrow keys. It shows the shared focus ring.
- Without scrollLabel, the container adds no tab stop and no name. Some browsers let keyboard users focus a container that scrolls. Do not count on it.
- Header cells use muted-foreground and body text uses the page's foreground. The theme tests hold both to 4.5:1 on the page and on cards.
- The row hover fill and the footer fill are muted at 50% over the page. Text on them sits between two pairs the theme tests hold to 4.5:1.
Examples
Invoices with a caption and a total
<Table>
<TableCaption>Recent invoices</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">INV-001</TableCell>
<TableCell>Paid</TableCell>
<TableCell className="text-right">$250.00</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">INV-002</TableCell>
<TableCell>Pending</TableCell>
<TableCell className="text-right">$150.00</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={2}>Total</TableCell>
<TableCell className="text-right">$400.00</TableCell>
</TableRow>
</TableFooter>
</Table>A wide table on a phone
<Table scrollLabel="Team members" size="sm">
<TableCaption>Team members</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role</TableHead>
<TableHead>Last active</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Ada Lovelace</TableCell>
<TableCell>ada@example.com</TableCell>
<TableCell>Owner</TableCell>
<TableCell>2 minutes ago</TableCell>
</TableRow>
</TableBody>
</Table>Row headers
<Table>
<TableCaption>Plan limits</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Plan</TableHead>
<TableHead className="text-right">Seats</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableHead scope="row">Free</TableHead>
<TableCell className="text-right">3</TableCell>
</TableRow>
<TableRow>
<TableHead scope="row">Pro</TableHead>
<TableCell className="text-right">25</TableCell>
</TableRow>
</TableBody>
</Table>No rows yet
<Table>
<TableCaption>Orders</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Order</TableHead>
<TableHead>Customer</TableHead>
<TableHead className="text-right">Total</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell colSpan={3} className="h-24 text-center text-muted-foreground">
No orders yet.
</TableCell>
</TableRow>
</TableBody>
</Table>