Page views this month| Page | Views | Updated |
|---|
| Home | 1,204 | Oct 2026 |
|---|
| Design System | 386 | Oct 2026 |
|---|
| Components | 92 | Oct 2026 |
|---|
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
} from "@/components/Table";
const pages = [
{ page: "Home", views: 1204, updated: "Oct 2026" },
{ page: "Design System", views: 386, updated: "Oct 2026" },
{ page: "Components", views: 92, updated: "Oct 2026" },
];
export function TableExample() {
return (
<Table caption="Page views this month">
<TableHead>
<TableRow>
<TableHeaderCell>Page</TableHeaderCell>
<TableHeaderCell align="end">Views</TableHeaderCell>
<TableHeaderCell>Updated</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{pages.map(({ page, views, updated }) => (
<TableRow key={page}>
<TableHeaderCell>{page}</TableHeaderCell>
<TableCell align="end">{views.toLocaleString("en-US")}</TableCell>
<TableCell>{updated}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}
Examples
Basic
Keyboard-focusable scrollers| Engine | Browsers | Scrollers focusable by default |
|---|
| Blink | Chrome, Edge, Opera | Yes, from Chrome 130, when nothing inside is focusable |
|---|
| Gecko | Firefox | Yes |
|---|
| WebKit | Safari | No |
|---|
Wide
Library keyboard support| Component | Tab | Shift+Tab | Enter | Space | Arrow keys | Home / End | Escape | A to Z |
|---|
| Button | Focuses it | Focuses it | Activates it | Activates it | — | — | — | — |
|---|
| Link | Focuses it | Focuses it | Follows it | Scrolls the page | — | — | — | — |
|---|
| Menu | Closes, moves on | Closes, moves back | Opens; chooses an item | Opens; chooses or toggles | Opens; next or previous item | First or last item | Closes | Next matching item |
|---|
| Accordion | Next heading or panel | Previous heading or panel | Opens or closes | Opens or closes | Scroll the page | — | — | — |
|---|
| Tooltip | Shows it | Shows it | — | — | — | — | Hides it | — |
|---|
| CodeBlock | Copy button, then the code | The code, then the copy button | Copies | Copies | Scroll the focused code | — | — | — |
|---|
| Table | The table, while it's too wide | The table, while it's too wide | — | — | Scroll the focused table | — | — | — |
|---|
Tables too wide for the panel scroll inside their box. While one scrolls, it's a Tab stop:
Announced as: “Library keyboard support, group”
Example build sizes| File | Size (kB) |
|---|
| Raw | Gzip |
|---|
| index.html | 4.2 | 1.6 |
|---|
| entry.client.js | 182.4 | 57.9 |
|---|
| root.css | 21.3 | 4.8 |
|---|
| Total | 207.9 | 64.3 |
|---|
Hidden caption
Theme keys
Theme keys| Stored value | The page shows |
|---|
light | The Light theme, whatever the system uses |
|---|
dark | The Dark theme, whatever the system uses |
|---|
Announced as: “Theme keys, table”
Customized with component tokens
Keyboard-focusable scrollers| Engine | Browsers | Scrollers focusable by default |
|---|
| Blink | Chrome, Edge, Opera | Yes, from Chrome 130, when nothing inside is focusable |
|---|
| Gecko | Firefox | Yes |
|---|
| WebKit | Safari | No |
|---|
.customTable {
--component-table-header-background: var(--color-accent-background);
--component-table-border: var(--color-accent-border);
}
Props
Any native prop not listed here goes to the root element unchanged.
TableHead, TableBody, TableFoot and TableRow take only children and their element's native props. className and native props reach each part's element, and Table's reach the root div. caption holds no links or buttons. Native thead, tr, th and td inside a Table get the look, but not the automatic scope.
Table props| Prop | Type | Default | Description |
|---|
caption | ReactNode | — | Required. Names the table, and its focusable group while it scrolls. Shown above the panel unless hideCaption. No interactive content. |
|---|
hideCaption | boolean | false | Hides the caption visually; it still names the table for assistive technology. Use it only when visible text right before the table already says the same thing. |
|---|
children | ReactNode | — | Required. TableHead, TableBody and TableFoot (or native thead, tbody and tfoot), after any colgroup elements. |
|---|
TableHeaderCell props| Prop | Type | Default | Description |
|---|
scope | "col" | "row" | "colgroup" | "rowgroup" | — | Which cells it heads. Unset: "col" in TableHead, "row" in TableBody or TableFoot, and none elsewhere. Set it for a column header in the body, or to "colgroup" for a header over a column group, which needs a matching <colgroup span> before the head. |
|---|
align | "start" | "center" | "end" | "start" | Text alignment in the cell. Use "end" for numeric columns, so digits line up. |
|---|
children | ReactNode | — | Required. The header text. |
|---|
TableCell props| Prop | Type | Default | Description |
|---|
align | "start" | "center" | "end" | "start" | Text alignment in the cell. Use "end" for numeric columns, so digits line up. |
|---|
children | ReactNode | — | Required. The cell's content. |
|---|
Accessibility
- The caption names the table, and its focusable group while it scrolls. hideCaption hides it visually but keeps it for assistive technology.
- A header cell's scope comes from where it sits: "col" in TableHead, "row" in TableBody and TableFoot. An explicit scope always wins.
- While it's too wide to fit, the table is a focusable group named by its caption, so a keyboard can scroll it. Safari doesn't make scrolling boxes focusable on its own, and Chrome only does when nothing inside is focusable. Once it fits, it's no longer a Tab stop. Before the page's script runs, every table is one.
- It's a group, not a region, so a page of tables doesn't fill the landmarks list.
- The stripes, border and lines are decorative: the table's semantics carry its rows and columns. In forced colors the stripes go, and each body row gets a line instead.
- Use a table for tabular data, never for layout.
Table keyboard| Key | Action |
|---|
| Tab / Shift+Tab | Moves to the table while it's too wide to fit, then to any controls in its cells. |
|---|
| ← / → | Scrolls the focused table sideways. |
|---|