Native Table
Display tabular content, such as a report, in a native HTML table where each control in a cell keeps its own tab stop.
| Invoice | Customer | Status | Issued | Amount |
|---|---|---|---|---|
| INV-2041 | Brightline Labs | Sep 2, 2026 | $4,800.00 | |
| INV-2042 | Harbor & Pine | Sep 15, 2026 | $1,250.00 | |
| INV-2043 | Northfield Health | Oct 1, 2026 | $12,400.00 | |
| INV-2044 | Quanta Logistics | Oct 14, 2026 | $860.00 | |
| INV-2045 | Sora Studio | Oct 27, 2026 | $3,175.00 |
Installation
pnpm dlx shadcn@4.18.0 add @siteplane/native-tableComplete the one-time setup first. Registry JSON lists source files and dependencies.
For this page’s examples, also install:
pnpm dlx shadcn@4.18.0 add @siteplane/badge @siteplane/button @siteplane/card @siteplane/input @siteplane/popoverNative Table uses the stylesheet of Table, so installing it also installs the Table item and react-aria-components.
Usage
import {
NativeTable,
NativeTableBody,
NativeTableCell,
NativeTableHead,
NativeTableHeader,
NativeTableRow,
} from "@/siteplane/components/native-table";
<NativeTable aria-label="Invoices">
<NativeTableHeader>
<NativeTableRow>
<NativeTableHead>Invoice</NativeTableHead>
<NativeTableHead align="end">Amount</NativeTableHead>
</NativeTableRow>
</NativeTableHeader>
<NativeTableBody>
<NativeTableRow>
<NativeTableHead scope="row">INV-2041</NativeTableHead>
<NativeTableCell align="end">$4,800.00</NativeTableCell>
</NativeTableRow>
</NativeTableBody>
</NativeTable>;Native Table or Table
Native Table shares the stylesheet of Table, so its heading row, row lines, cell spacing and footer look the same. The two differ in behavior, so choose by what people do with the rows.
Use Native Table for content that people read, such as a report, a price list, or a table in rendered markdown or in an email that your application displays. It renders the browser’s own table elements and adds no behavior to them, and assistive technology presents it as a table. A control in one of its cells, such as an input or a button, stays an ordinary control with its own tab stop. Tab reaches the controls in reading order, and the arrow keys stay with the control in focus, where they move the caret of an input.
Rows that people select, sort or act on belong in Table, a React Aria grid. The grid is a single tab stop: the arrow keys move through its rows and cells, and Tab leaves the table.
Composition
NativeTable
├── NativeTableCaption (optional, first)
├── NativeTableHeader
│ └── NativeTableRow
│ └── NativeTableHead
├── NativeTableBody
│ └── NativeTableRow
│ ├── NativeTableHead scope="row"
│ └── NativeTableCell
└── NativeTableFooter (optional)
└── NativeTableRow
├── NativeTableHead scope="row"
└── NativeTableCellEach part renders the HTML element it is named after. NativeTable is a table inside a div that scrolls horizontally, NativeTableHeader, NativeTableBody and NativeTableFooter are thead, tbody and tfoot, NativeTableRow is a tr, NativeTableHead a th and NativeTableCell a td. As in HTML, the heading row is a NativeTableRow inside NativeTableHeader, and rows need no IDs. NativeTableHead heads its column by default; in a body row, scope="row" makes it the heading of its row.
Caption and footer
Place NativeTableCaption first inside NativeTable, where HTML expects the caption. It renders below the table and names it for assistive technology, so the table needs no aria-label. NativeTableFooter holds summary rows, which sit on the subtle surface at medium weight after a line, as in Table. A footer row with tone="total" sets its text in the strong weight. Your application calculates the totals.
| Plan | Customers | Monthly price | Revenue |
|---|---|---|---|
| Starter | 1,204 | $12 | $14,448 |
| Team | 386 | $49 | $18,914 |
| Business | 92 | $199 | $18,308 |
| Total | 1,682 | $51,670 |
Cells with controls
A cell can hold any control, such as an Input or a Button that opens a Popover. Each control keeps its own tab stop: in this estimate, Tab moves from the Research estimate to its Edit note button and on to the Prototyping estimate. The arrow keys, Enter and Space act on the control in focus alone, since the table handles no keys. Give a control without a visible label an aria-label that names its row, such as Research estimate.
| Item | Estimate ($) | Notes |
|---|---|---|
| Design | ||
| Research | ||
| Prototyping | Interactive prototype | |
| Amounts exclude tax. | ||
| Approved budget | $4,000 | |
The table changes nothing about its controls, so the inputs in its cells take part in a form around the table like any other input. Inside CardFrameBleed, the first and last columns take the frame’s inset, so the table lines up with the frame header, as Table does.
Row tones
tone on NativeTableRow gives a row its role in a report. A section row heads a group of rows on a light tint in the strong weight. Give it one NativeTableHead with scope="rowgroup" and a colSpan across the columns, as Design in the estimate above, and turn off its hover with hoverable={false}. A total row sums up on a stronger tint, also in the strong weight; inside NativeTableFooter it keeps the footer’s surface instead of adding a second tint. A muted row sets a note, such as Amounts exclude tax, in the quieter text.
Alignment
align on NativeTableHead and NativeTableCell sets the text to the start of the cell, the default, to its center or to its end. Start and end follow the writing direction, so they swap sides in a right-to-left layout. end also sets tabular numbers, so the digits of amounts line up; give the heading of a number column the alignment of its cells. The tables under Sizes center their plan columns.
Sizes
size sets the height of the heading row and the space above and below the content of each cell. At sm the heading row is 32 px tall and a cell keeps 4 px above and below its content; md sets 36 and 6 px, lg 40 and 8 px, and xl 48 and 12 px. Without size, the table takes the provider’s control size, lg by default.
| Limit | Starter | Team |
|---|---|---|
| Projects | 3 | Unlimited |
| Members | 5 | 50 |
| Limit | Starter | Team |
|---|---|---|
| Projects | 3 | Unlimited |
| Members | 5 | 50 |
| Limit | Starter | Team |
|---|---|---|
| Projects | 3 | Unlimited |
| Members | 5 | 50 |
| Limit | Starter | Team |
|---|---|---|
| Projects | 3 | Unlimited |
| Members | 5 | 50 |
Markdown tables
Typeset renders the tables of rendered markdown with Native Table. Its typesetComponents map passes table, thead, tbody, tfoot, tr, th and td to the matching Native Table parts with the hover turned off, since nothing in written text acts, and gives each cell the alignment of its column. Installing Typeset installs Native Table.
API reference
NativeTable
| Prop | Type | Default | Description |
|---|---|---|---|
size | sm | md | lg | xl | provider / lg | Height of the heading row and space above and below the cell content: 32 and 4 px at sm, 36 and 6 px at md, 40 and 8 px at lg, 48 and 12 px at xl. |
hoverable | boolean | true | Shows the hover background of the heading row and the body rows. false turns it off for the whole table. |
min | CSS min-width | unset | Minimum width of the table. In a narrower container, the table keeps this width and scrolls horizontally. |
class | string | unset | Styles the table element. |
container | string | unset | Styles the scrolling container around the table. |
aria-label / aria-labelledby | string | unset | Accessible name of the table. A NativeTableCaption names the table without them. |
NativeTableRow
| Prop | Type | Default | Description |
|---|---|---|---|
tone | section | total | muted | unset | section heads a group of rows on a light tint and total sums up on a stronger tint, both in the strong weight; muted sets a note in the quieter text. |
hoverable | boolean | true | Shows the hover background of a body row. The table’s hoverable turns it off for every row. |
class | string | unset | Local row styling. |
NativeTableHead and NativeTableCell
| Prop | Type | Default | Description |
|---|---|---|---|
align | start | center | end | start | Text alignment in the writing direction. end also sets tabular numbers. |
Native | col | row | rowgroup | colgroup | col | The cells the heading names. col names its column, row the other cells of its row and rowgroup the rows of its group. A row or group heading takes the text style of its row. |
col | number | 1 | Number of columns or rows the cell spans. |
class | string | unset | Local cell styling. |
Caption, header, body and footer
| Prop | Type | Default | Description |
|---|---|---|---|
Native | caption props | — | The caption, below the table, which names it. Place it first inside NativeTable. |
Native | thead props | — | The heading row: a NativeTableRow of NativeTableHead cells. |
Native | tbody props | — | The body rows. |
Native | tfoot props | — | Summary rows on the subtle surface at medium weight, after a line. |
Every part takes the props of its HTML element, a ref to that element and a
string className. NativeTable passes its props to the table, not to the
scrolling div. NativeTableProps, NativeTableRowProps,
NativeTableHeadProps and NativeTableCellProps are exported. The
--table-edge-padding custom property sets the inline padding of the first and
last columns, and CardFrameBleed sets it to the frame’s inset.
Usage notes
Give every table an accessible name, with aria-label or a NativeTableCaption. Make the cell that names each row a row heading, a NativeTableHead with scope="row", so a screen reader announces it with the other cells of its row.
The table fills the width of its container. Headings stay on one line, and the text in a cell wraps. When the columns need more room than a narrow screen leaves them, set minWidth: the table then keeps that width and scrolls horizontally inside its container, and the page does not.
Native Table holds no state. It renders every row it is given, and sorting, filtering, paging and totals belong to your application. A control in a cell keeps its own value and events.