Components
A responsive table component.
Playground
| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV001 | Paid | Credit Card | $250.00 |
| INV002 | Pending | PayPal | $150.00 |
| INV003 | Unpaid | Bank Transfer | $350.00 |
| INV004 | Paid | Credit Card | $450.00 |
| Total | $1,200.00 | ||
Table, TableHeader, TableBody, TableRow, TableHead and TableCell. The root wraps the table in an overflow-x-auto container, so wide tables scroll instead of breaking the layout, and TableCaption renders below the rows.| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV001 | Paid | Credit Card | $250.00 |
| INV002 | Pending | PayPal | $150.00 |
| INV003 | Unpaid | Bank Transfer | $350.00 |
| INV004 | Paid | Credit Card | $450.00 |
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@workspace/ui/components/table"
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-24">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-end">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.status}</TableCell>
<TableCell>{invoice.method}</TableCell>
<TableCell className="text-end">{invoice.amount}</TableCell>
</TableRow>
))}
</TableBody>
</Table>TableFooter closes the table with a bg-muted/50 summary band — use colSpan to stretch the label across the data columns.| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV001 | Paid | Credit Card | $250.00 |
| INV002 | Pending | PayPal | $150.00 |
| INV003 | Unpaid | Bank Transfer | $350.00 |
| INV004 | Paid | Credit Card | $450.00 |
| Total | $1,200.00 | ||
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-24">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-end">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.status}</TableCell>
<TableCell>{invoice.method}</TableCell>
<TableCell className="text-end">{invoice.amount}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-end">$1,200.00</TableCell>
</TableRow>
</TableFooter>
</Table>bg-muted/50 on hover and to solid bg-muted when marked with data-state="selected" — the hook selection libraries like TanStack Table attach to.| Invoice | Status | Amount |
|---|---|---|
| INV001 | Paid | $250.00 |
| INV002 | Pending | $150.00 |
| INV003 | Unpaid | $350.00 |
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-24">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-end">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">INV001</TableCell>
<TableCell>Paid</TableCell>
<TableCell className="text-end">$250.00</TableCell>
</TableRow>
<TableRow data-state="selected">
<TableCell className="font-medium">INV002</TableCell>
<TableCell>Pending</TableCell>
<TableCell className="text-end">$150.00</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">INV003</TableCell>
<TableCell>Unpaid</TableCell>
<TableCell className="text-end">$350.00</TableCell>
</TableRow>
</TableBody>
</Table>DropdownMenu for per-row actions. Keep the trigger compact with size="icon" and give it an sr-only label.| Product | Price | Actions |
|---|---|---|
| Wireless Mouse | $29.99 | |
| Mechanical Keyboard | $129.00 | |
| USB-C Hub | $49.50 |
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@workspace/ui/components/dropdown-menu"
import { Button } from "@workspace/ui/components/button"
import { DotsThreeIcon } from "@phosphor-icons/react"
<Table>
<TableHeader>
<TableRow>
<TableHead>Product</TableHead>
<TableHead>Price</TableHead>
<TableHead className="text-end">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{products.map((item) => (
<TableRow key={item.product}>
<TableCell className="font-medium">{item.product}</TableCell>
<TableCell>{item.price}</TableCell>
<TableCell className="text-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="size-8">
<DotsThreeIcon />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>dir="rtl" on a wrapper or inherit it from a Direction provider — cells use logical properties (text-start, pe-0), so column alignment mirrors automatically.| الفاتورة | الحالة | المبلغ |
|---|---|---|
| INV001 | مدفوعة | $250.00 |
| INV002 | قيد الانتظار | $150.00 |
<div dir="rtl">
<Table>
<TableCaption>قائمة بفواتيرك الأخيرة.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-24">الفاتورة</TableHead>
<TableHead>الحالة</TableHead>
<TableHead className="text-end">المبلغ</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">INV001</TableCell>
<TableCell>مدفوعة</TableCell>
<TableCell className="text-end">$250.00</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">INV002</TableCell>
<TableCell>قيد الانتظار</TableCell>
<TableCell className="text-end">$150.00</TableCell>
</TableRow>
</TableBody>
</Table>
</div>Table adds structure: a div wrapper that provides horizontal scrolling.| Part | Renders |
|---|---|
| Table | div > table |
| TableHeader | thead |
| TableBody | tbody |
| TableFooter | tfoot |
| TableRow | tr |
| TableHead | th |
| TableCell | td |
| TableCaption | caption |
Tokens used
7 design tokens consumed by the Table component.
--muted
Muted
--foreground
Foreground
--muted-foreground
Muted Foreground
--border
Border
--text-sm
Text Small (14px)
--font-weight-medium
Font weight Medium (500)
--spacing
Spacing unit (4px)