Home

Table

Rows and columns of records under a header row, scrolling sideways with a fade when wider than its box.

ui.tablecomponents/ui/table.tsx

Use case

For the AI

Use a table when each row is one record and the columns compare the same facts: invoices by number, client, status and amount, orders by date and total, clients by city and balance. Status cells hold a Badge and amounts sit right-aligned in tabular figures. Wrap it in a rounded white box with overflow-hidden for its frame.

Use it when

  • A list of invoices, orders or clients with the same facts per row
  • Figures a person compares down a column
  • Rows that open a record when clicked
  • A short summary with a totals row (TableFooter)

Use something else when

  • One record's facts on its own page: Card
  • Moving through a long list page by page, under the table: Pagination
  • The rows are still loading: Skeleton
  • There are no rows yet: Empty state

Examples

InvoiceClientStatusAmount
INV-2026-031Bistro Le MarchandPaid$4,885.00
INV-2026-032Hôtel Rive-NordDue soon$1,240.00
INV-2026-033Clinique BelvédèreOverdue$860.00
INV-2026-034Brasserie La VoûteDraft$2,100.00

Code

Import

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

Usage

const TONE = { Paid: "success", "Due soon": "warning", Overdue: "error", Draft: "secondary" } as const

type Row = { number: string; client: string; status: keyof typeof TONE; amount: string }

export function InvoicesTable({ rows }: { rows: Row[] }) {
  return (
    <div className="overflow-hidden rounded-[20px] bg-background shadow-(--shadow-rest)">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead className="ps-5">Invoice</TableHead>
            <TableHead>Client</TableHead>
            <TableHead>Status</TableHead>
            <TableHead className="pe-5 text-right">Amount</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {rows.map((row) => (
            <TableRow key={row.number}>
              <TableCell className="ps-5 font-medium tabular-nums">{row.number}</TableCell>
              <TableCell className="text-ink-2">{row.client}</TableCell>
              <TableCell>
                <Badge variant={TONE[row.status]}>{row.status}</Badge>
              </TableCell>
              <TableCell className="pe-5 text-right tabular-nums">{row.amount}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  )
}

Rules

  • The frame goes on a wrapper (rounded-[20px], overflow-hidden, bg-background, the rest shadow), never on Table: its container scrolls sideways with scroll-fade-x, and the fade would erase the frame.
  • Numbers are tabular-nums, and amounts are right-aligned (text-right on the head and on the cell).
  • Inside the frame the first column takes ps-5 and the last pe-5.
  • A status is a Badge, its variant mapped from one TONE object.
  • Headers are short and in sentence case (Invoice, Client, Status, Amount); a selected row carries data-state="selected".