Table

Structured data display with sortable column headers, striped rows, three density settings, and optional column borders. Composed from semantic sub-components.

Preview

NameRoleStatusJoined
AKAmir Karimi
DeveloperBusyJan 30, 2024
DBDev Bot
BotOnlineFeb 14, 2024
JRJamieson Rothwell
AdminOnlineJan 12, 2023
LMLena Müller
DesignerOfflineJun 18, 2023
SPSam Park
DeveloperAwayMar 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

NameRoleStatus
Jamieson RothwellAdminActive
Sam ParkDeveloperAway

default

NameRoleStatus
Jamieson RothwellAdminActive
Sam ParkDeveloperAway

comfortable

NameRoleStatus
Jamieson RothwellAdminActive
Sam ParkDeveloperAway

Striped

The striped prop alternates row backgrounds using a CSS child-selector applied via a data-striped attribute on the table root.

PipelineBranchStatusDuration
Production deploymainPassing2m 14s
PR previewfeat/navRunning1m 42s
Lint + typecheckmainPassing0m 38s
E2E testsmainFailed8m 03s

Bordered columns

Add bordered to draw vertical dividers between columns — useful for comparison tables or spreadsheet-style layouts.

FeatureStarterProEnterprise
Projects3UnlimitedUnlimited
Storage1 GB50 GBCustom
Team members110Unlimited
SSO
Priority support
PriceFree$12/moCustom

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.

Table.tsx
tsx
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

PropTypeDefaultDescription
stripedbooleanfalseApplies alternating row background on even rows.
density"default" | "compact" | "comfortable""default"Controls vertical cell padding.
borderedbooleanfalseAdds vertical dividers between columns.

TableHead props

PropTypeDefaultDescription
sortablebooleanRenders sort chevrons and makes the header clickable.
sortDirection"asc" | "desc" | falseCurrent sort direction. false = unsorted but sortable.
onSort() => voidCalled when the header is clicked.

Anatomy

ComponentElementNotes
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.

9:41

Pages

Homepage

/

Live
Visits12.4k

Pricing

/pricing

Live
Visits3.1k

Changelog

/log

Draft
Visits

API docs

/api

Live
Visits8.9k
ScenarioGuidance
Horizontal scrollWrap 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 columnWhen scrolling horizontally, make the first column sticky (position: sticky; left: 0; z-index: 1; background: ...) so the row label stays visible.
Card-stack layoutFor 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 priorityOn narrow screens, hide lower-priority columns with hidden md:table-cell. Show the most important 2–3 columns by default.
Row tap targetsMake 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.