Table
A responsive table component with a caption, header, body, and footer, plus an optional card row variant.
Last updated August 8, 2026
CreditsModifiedPublished
Copied from shadcn/ui and cossui.
- Replaced text-left with text-start, and pr-0 with pe-0 on the checkbox column, for RTL
- Added an optional card variant (Table variant="card") that separates each row into its own bordered, elevated surface — inspired by cossui's Table
Overview
| Invoice | Status | Method | Amount |
|---|---|---|---|
| INV001 | Paid | Credit Card | $250.00 |
| INV002 | Pending | PayPal | $150.00 |
| INV003 | Unpaid | Bank Transfer | $350.00 |
| Total | $750.00 | ||
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const invoices = [
{
invoice: "INV001",
paymentStatus: "Paid",
totalAmount: "$250.00",
paymentMethod: "Credit Card",
},
{
invoice: "INV002",
paymentStatus: "Pending",
totalAmount: "$150.00",
paymentMethod: "PayPal",
},
{
invoice: "INV003",
paymentStatus: "Unpaid",
totalAmount: "$350.00",
paymentMethod: "Bank Transfer",
},
]
export function TableDemoExample() {
return (
<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.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-end">{invoice.totalAmount}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-end">$750.00</TableCell>
</TableRow>
</TableFooter>
</Table>
)
}Installation
$ npx shadcn@latest add https://ui.persian-labs.ir/r/table.jsonUsage
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
export function Example() {
return (
<Table>
<TableCaption>A list of your recent invoices.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Invoice</TableHead>
<TableHead className="text-end">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>INV001</TableCell>
<TableCell className="text-end">$250.00</TableCell>
</TableRow>
</TableBody>
</Table>
)
}Card Variant
Set variant="card" for a card-style table: separated borders, rounded corners, and row surfaces that read as individual cards. The default variant is a simpler grid with shared borders.
| Project | Status | Team | Budget |
|---|---|---|---|
| Website Redesign | Paid | Frontend Team | $12,500 |
| Mobile App | Unpaid | Mobile Team | $8,750 |
| API Integration | Pending | Backend Team | $5,200 |
| Database Migration | Paid | DevOps Team | $3,800 |
| Total Budget | $30,250 | ||
import { Badge } from "@/components/ui/badge"
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const statusDotClass: Record<string, string> = {
Paid: "bg-emerald-500",
Unpaid: "bg-muted-foreground/50",
Pending: "bg-amber-500",
Failed: "bg-destructive",
}
const projects = [
{ project: "Website Redesign", status: "Paid", team: "Frontend Team", budget: "$12,500" },
{ project: "Mobile App", status: "Unpaid", team: "Mobile Team", budget: "$8,750" },
{ project: "API Integration", status: "Pending", team: "Backend Team", budget: "$5,200" },
{ project: "Database Migration", status: "Paid", team: "DevOps Team", budget: "$3,800" },
]
export function TableCardExample() {
return (
<Table variant="card" className="w-full max-w-xl">
<TableHeader>
<TableRow>
<TableHead>Project</TableHead>
<TableHead>Status</TableHead>
<TableHead>Team</TableHead>
<TableHead className="text-end">Budget</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{projects.map((row) => (
<TableRow key={row.project}>
<TableCell className="font-medium">{row.project}</TableCell>
<TableCell>
<Badge variant="outline" className="gap-1.5">
<span
aria-hidden="true"
className={`size-1.5 rounded-full ${statusDotClass[row.status]}`}
/>
{row.status}
</Badge>
</TableCell>
<TableCell>{row.team}</TableCell>
<TableCell className="text-end">{row.budget}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total Budget</TableCell>
<TableCell className="text-end">$30,250</TableCell>
</TableRow>
</TableFooter>
</Table>
)
}Actions
A table showing per-row actions using a Dropdown Menu.
| Product | Price | Actions |
|---|---|---|
| Wireless Mouse | $29.99 | |
| Mechanical Keyboard | $129.99 | |
| USB-C Hub | $49.99 |
import { MoreHorizontalIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const products = [
{ name: "Wireless Mouse", price: "$29.99" },
{ name: "Mechanical Keyboard", price: "$129.99" },
{ name: "USB-C Hub", price: "$49.99" },
]
export function TableActionsExample() {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Product</TableHead>
<TableHead>Price</TableHead>
<TableHead className="text-end">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{products.map((product) => (
<TableRow key={product.name}>
<TableCell className="font-medium">{product.name}</TableCell>
<TableCell>{product.price}</TableCell>
<TableCell className="text-end">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="size-8">
<MoreHorizontalIcon />
<span className="sr-only">Open menu</span>
</Button>
}
/>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}Selection
Combine the card variant with Checkbox row selection, Avatar, a status Badge, and a Dropdown Menu for row actions — a common shape for team or user management tables.
| Member | Role | Status | Actions | |
|---|---|---|---|---|
SASara Ahmadi | Owner | Active | ||
KRKian Rahimi | Admin | Active | ||
NZNiloofar Zare | Member | Invited |
"use client"
import { MoreHorizontalIcon } from "lucide-react"
import * as React from "react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const members = [
{ id: "1", name: "Sara Ahmadi", initials: "SA", role: "Owner", status: "active" as const },
{ id: "2", name: "Kian Rahimi", initials: "KR", role: "Admin", status: "active" as const },
{ id: "3", name: "Niloofar Zare", initials: "NZ", role: "Member", status: "invited" as const },
]
export function TableTeamExample() {
const [selected, setSelected] = React.useState<string[]>([])
const allSelected = selected.length === members.length
return (
<Table variant="card" className="w-full max-w-xl">
<TableHeader>
<TableRow>
<TableHead className="w-10">
<Checkbox
checked={allSelected}
onCheckedChange={(checked) =>
setSelected(checked ? members.map((m) => m.id) : [])
}
aria-label="Select all"
/>
</TableHead>
<TableHead>Member</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-end">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{members.map((member) => (
<TableRow
key={member.id}
data-state={selected.includes(member.id) ? "selected" : undefined}
>
<TableCell>
<Checkbox
checked={selected.includes(member.id)}
onCheckedChange={(checked) =>
setSelected((current) =>
checked
? [...current, member.id]
: current.filter((id) => id !== member.id)
)
}
aria-label={`Select ${member.name}`}
/>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Avatar size="sm">
<AvatarFallback>{member.initials}</AvatarFallback>
</Avatar>
<span className="font-medium">{member.name}</span>
</div>
</TableCell>
<TableCell className="text-muted-foreground">
{member.role}
</TableCell>
<TableCell>
<Badge variant={member.status === "active" ? "default" : "outline"}>
{member.status === "active" ? "Active" : "Invited"}
</Badge>
</TableCell>
<TableCell className="text-end">
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="ghost" size="icon" className="size-8">
<MoreHorizontalIcon />
<span className="sr-only">Open menu</span>
</Button>
}
/>
<DropdownMenuContent align="end">
<DropdownMenuItem>Change role</DropdownMenuItem>
<DropdownMenuItem>Resend invite</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
Remove
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}RTL
Header and cell alignment use logical text-start / text-end utilities, so numeric columns stay aligned to the trailing edge in both directions.
| فاکتور | وضعیت | روش پرداخت | مبلغ |
|---|---|---|---|
| INV001 | پرداختشده | کارت بانکی | ۲۵۰,۰۰۰ تومان |
| INV002 | در انتظار | کیف پول | ۱۵۰,۰۰۰ تومان |
| INV003 | پرداختنشده | حواله بانکی | ۳۵۰,۰۰۰ تومان |
| مجموع | ۷۵۰,۰۰۰ تومان | ||
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const invoices = [
{
invoice: "INV001",
paymentStatus: "پرداختشده",
totalAmount: "۲۵۰,۰۰۰ تومان",
paymentMethod: "کارت بانکی",
},
{
invoice: "INV002",
paymentStatus: "در انتظار",
totalAmount: "۱۵۰,۰۰۰ تومان",
paymentMethod: "کیف پول",
},
{
invoice: "INV003",
paymentStatus: "پرداختنشده",
totalAmount: "۳۵۰,۰۰۰ تومان",
paymentMethod: "حواله بانکی",
},
]
export function TableRtlExample() {
return (
<Table>
<TableCaption>لیست فاکتورهای اخیر شما.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-24">فاکتور</TableHead>
<TableHead>وضعیت</TableHead>
<TableHead>روش پرداخت</TableHead>
<TableHead className="text-end">مبلغ</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invoices.map((invoice) => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-end">{invoice.totalAmount}</TableCell>
</TableRow>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>مجموع</TableCell>
<TableCell className="text-end">۷۵۰,۰۰۰ تومان</TableCell>
</TableRow>
</TableFooter>
</Table>
)
}