Table

A structured data table built from semantic HTML elements. Supports headers, body rows, an optional footer for summaries, and an optional caption. No sorting or pagination built in — compose those separately.

Anatomy

<Table> is the scrollable wrapper around a <table>. TableHeader wraps the <thead> section. TableBody wraps <tbody>. TableFooter wraps <tfoot> for summary rows. TableRow is a <tr>, TableHead a <th>, and TableCell a <td>. TableCaption renders an accessible caption above the table.

InvoiceStatusMethodAmount
INV001PaidCredit Card$250.00
INV002PendingPayPal$150.00

Examples

Full table — header, body, footer

InvoiceStatusMethodAmount
INV001PaidCredit Card$250.00
INV002PendingPayPal$150.00
INV003OverdueBank Transfer$350.00
INV004PaidCredit Card$450.00
Total$1,200.00

With caption

Recent invoices for your account.
InvoiceMethodAmount
INV001Credit Card$250.00
INV002PayPal$150.00
INV003Bank Transfer$350.00

Design Guidelines

Do

  • Right-align numeric columns. Numbers are easier to compare when they share the same right edge — use className="text-right" on both the header and cells.
  • Use TableFooter for totals. Summary rows belong in <tfoot> — this is semantically correct and visually distinct.
  • Add a TableCaption for data tables. Captions improve accessibility — screen readers announce them before reading the table.

Don't

  • Don't put too many columns on mobile. Tables don't reflow — wrap them in a scroll container or hide less important columns at small breakpoints.
  • Don't use tables for layout. Tables are for structured data. Use CSS grid or flexbox for page layout.
  • Don't skip the header row. Every data table needs a TableHeader — it communicates column semantics to assistive technology.

Developer Reference

Accessibility

  • TableHead renders <th scope="col"> — screen readers use this to associate header cells with data cells.
  • TableCaption renders a <caption> element, which is announced before the table content by screen readers.
  • The <Table> wrapper adds overflow-x-auto so wide tables scroll horizontally rather than breaking the layout.
  • For sortable columns, add aria-sort to the relevant TableHead and update it on interaction.

Usage

import {
  Table,
  TableHeader,
  TableBody,
  TableFooter,
  TableRow,
  TableHead,
  TableCell,
  TableCaption,
} from "@/components/ui/table"

<Table>
  <TableCaption>Recent invoices.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead className="text-right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {invoices.map((inv) => (
      <TableRow key={inv.id}>
        <TableCell className="font-medium">{inv.id}</TableCell>
        <TableCell>{inv.status}</TableCell>
        <TableCell className="text-right">{inv.amount}</TableCell>
      </TableRow>
    ))}
  </TableBody>
  <TableFooter>
    <TableRow>
      <TableCell colSpan={2}>Total</TableCell>
      <TableCell className="text-right">$1,200.00</TableCell>
    </TableRow>
  </TableFooter>
</Table>