3

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

A list of your recent invoices.
InvoiceStatusMethodAmount
INV001PaidCredit Card$250.00
INV002PendingPayPal$150.00
INV003UnpaidBank 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.json

Usage

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.

ProjectStatusTeamBudget
Website RedesignPaidFrontend Team$12,500
Mobile AppUnpaidMobile Team$8,750
API IntegrationPendingBackend Team$5,200
Database MigrationPaidDevOps 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.

ProductPriceActions
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.

MemberRoleStatusActions
SASara Ahmadi
OwnerActive
KRKian Rahimi
AdminActive
NZNiloofar Zare
MemberInvited
"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>
  )
}

API Reference

Table