Table

Structural table primitives — no logic, just the seam surface. The table is a raised key resting on the canvas; the header is a quiet label rail; rows are separated by hairlines and tint on select. For a data grid with sorting, filtering, pagination and inline edit, use Data Table.

A list of your recent invoices.
InvoiceStatusMethodAmount
INV-001PaidCredit Card$250.00
INV-002PendingPayPal$150.00
INV-003UnpaidBank Transfer$350.00
INV-004PaidCredit Card$450.00
Total$1,200.00
tsx
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from "@/registry/seam/ui/table"

const invoices = [
  {
    invoice: "INV-001",
    status: "Paid",
    method: "Credit Card",
    total: "$250.00",
  },
  { invoice: "INV-002", status: "Pending", method: "PayPal", total: "$150.00" },
  {
    invoice: "INV-003",
    status: "Unpaid",
    method: "Bank Transfer",
    total: "$350.00",
  },
  {
    invoice: "INV-004",
    status: "Paid",
    method: "Credit Card",
    total: "$450.00",
  },
]

export default function TableDemo() {
  return (
    <Table aria-label="Recent invoices">
      <TableCaption>A list of your recent invoices.</TableCaption>
      <TableHeader>
        <TableRow>
          <TableHead>Invoice</TableHead>
          <TableHead>Status</TableHead>
          <TableHead>Method</TableHead>
          <TableHead className="text-right">Amount</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {invoices.map((row) => (
          <TableRow key={row.invoice}>
            <TableCell className="font-medium">{row.invoice}</TableCell>
            <TableCell>{row.status}</TableCell>
            <TableCell>{row.method}</TableCell>
            <TableCell className="text-right tabular-nums">
              {row.total}
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
      <TableFooter>
        <TableRow>
          <TableCell colSpan={3}>Total</TableCell>
          <TableCell className="text-right tabular-nums">$1,200.00</TableCell>
        </TableRow>
      </TableFooter>
    </Table>
  )
}
bunx --bun @seamui/cli@latest add table

API

PropTypeDefaultDescription
containerClassNamestringClasses for the scrollable region wrapper (the raised surface), separate from the <table>.
aria-labelstringNames the scroll region so keyboard users landing on it know what it is.

Exports Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, and TableCaption — each carrying a data-slot and forwarding all native props.

Notes

  • When the table is wider than its container the wrapper becomes a focusable role="region" so it can be scrolled by keyboard, not just pointer — a table that fits adds no extra tab stop.
  • Set data-state="selected" on a TableRow to tint it — the row tints rather than embosses, keeping the checkbox as the one embossed token.
  • These primitives are static (no motion of their own). Animation lives in Data Table, which composes them.

Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.