Table
Structured data display with sortable column headers, striped rows, three density settings, and optional column borders. Composed from semantic sub-components.
Preview
| Name | Role | Status | Joined |
|---|---|---|---|
AKAmir Karimi | Developer | Busy | Jan 30, 2024 |
DBDev Bot | Bot | Online | Feb 14, 2024 |
JRJamieson Rothwell | Admin | Online | Jan 12, 2023 |
LMLena Müller | Designer | Offline | Jun 18, 2023 |
SPSam Park | Developer | Away | Mar 5, 2023 |
Density
Three density modes adjust vertical padding without changing font size. compact is ideal for data-dense dashboards; comfortable gives breathing room for editorial content.
compact
| Name | Role | Status |
|---|---|---|
| Jamieson Rothwell | Admin | Active |
| Sam Park | Developer | Away |
default
| Name | Role | Status |
|---|---|---|
| Jamieson Rothwell | Admin | Active |
| Sam Park | Developer | Away |
comfortable
| Name | Role | Status |
|---|---|---|
| Jamieson Rothwell | Admin | Active |
| Sam Park | Developer | Away |
Striped
The striped prop alternates row backgrounds using a CSS child-selector applied via a data-striped attribute on the table root.
| Pipeline | Branch | Status | Duration |
|---|---|---|---|
| Production deploy | main | Passing | 2m 14s |
| PR preview | feat/nav | Running | 1m 42s |
| Lint + typecheck | main | Passing | 0m 38s |
| E2E tests | main | Failed | 8m 03s |
Bordered columns
Add bordered to draw vertical dividers between columns — useful for comparison tables or spreadsheet-style layouts.
| Feature | Starter | Pro | Enterprise |
|---|---|---|---|
| Projects | 3 | Unlimited | Unlimited |
| Storage | 1 GB | 50 GB | Custom |
| Team members | 1 | 10 | Unlimited |
| SSO | — | — | ✓ |
| Priority support | — | ✓ | ✓ |
| Price | Free | $12/mo | Custom |
Implementation
Table is a pure server component. Variants are driven by data-* attributes on the root <table> so that Tailwind CSS child selectors ([table[data-striped]_&:nth-child(even)]) apply without passing props through every sub-component.
import {
Table, TableHeader, TableBody, TableFooter,
TableRow, TableHead, TableCell
} from "@/components/ui/Table";
// Basic
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Jamieson Rothwell</TableCell>
<TableCell>Active</TableCell>
<TableCell>Admin</TableCell>
</TableRow>
</TableBody>
</Table>
// Striped + compact
<Table striped density="compact">…</Table>
// Sortable column header
<TableHead
sortable
sortDirection={sortKey === "name" ? sortDir : false}
onSort={() => handleSort("name")}
>
Name
</TableHead>Table props
| Prop | Type | Default | Description |
|---|---|---|---|
striped | boolean | false | Applies alternating row background on even rows. |
density | "default" | "compact" | "comfortable" | "default" | Controls vertical cell padding. |
bordered | boolean | false | Adds vertical dividers between columns. |
TableHead props
| Prop | Type | Default | Description |
|---|---|---|---|
sortable | boolean | — | Renders sort chevrons and makes the header clickable. |
sortDirection | "asc" | "desc" | false | — | Current sort direction. false = unsorted but sortable. |
onSort | () => void | — | Called when the header is clicked. |
Anatomy
| Component | Element | Notes |
|---|---|---|
Table | <table> | Root. Accepts striped, density, bordered |
TableHeader | <thead> | Column header section |
TableBody | <tbody> | Row container with row dividers |
TableFooter | <tfoot> | Optional summary/totals row |
TableRow | <tr> | Row. Accepts selected for highlighted state |
TableHead | <th> | Column header cell. Accepts sortable, sortDirection, onSort |
TableCell | <td> | Data cell |
TableCaption | <caption> | Optional accessible description of the table |
Mobile
Data tables are one of the hardest components to use on mobile. The two main strategies are horizontal scrolling and a card-stack layout — choose based on the data density.
Pages
Homepage
/
Pricing
/pricing
Changelog
/log
API docs
/api
| Scenario | Guidance |
|---|---|
| Horizontal scroll | Wrap the table in overflow-x: auto. Add a subtle horizontal scroll indicator (fade gradient on the right edge) to hint there is more content off-screen. |
| Sticky first column | When scrolling horizontally, make the first column sticky (position: sticky; left: 0; z-index: 1; background: ...) so the row label stays visible. |
| Card-stack layout | For fewer than 6 columns, transform rows into cards on mobile. Each card shows the column header as a label above the cell value. Hide the <thead> in this mode. |
| Column priority | On narrow screens, hide lower-priority columns with hidden md:table-cell. Show the most important 2–3 columns by default. |
| Row tap targets | Make full rows tappable on mobile by adding an onClick and visual hover/active state. Cell-level tapping is too small to be reliable on touch. |
Accessibility
- →Use <TableCaption> to describe the table for screen readers — it is the first thing announced when focus enters the table.
- →Sortable TableHead sets aria-sort="ascending" / "descending" / "none" so screen readers announce the current sort state.
- →Column headers (<th>) have implicit scope="col" — assistive technology uses this to associate cells with headers.
- →Row headers can be created by passing scope="row" to a TableCell — useful for the first column of comparison tables.
- →Never use a table for layout. Use CSS grid or flexbox instead.
- →Horizontal overflow is handled by the wrapping div — avoid fixed-width columns that would clip content on small screens.