Embossed UI

Search documentation

Search for a page or component.

Table

A responsive table component.

A list of your recent invoices.
InvoiceStatusMethodAmount
INV001PaidCredit card$250.00
INV002PendingPayPal$150.00
INV003UnpaidBank transfer$350.00
INV004PaidCredit card$450.00
INV005PaidPayPal$550.00
INV006PendingBank transfer$200.00
INV007UnpaidCredit card$300.00
Total$2,250.00
import { Badge } from "@/components/ui/badge"
import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableFooter,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"

const invoices = [
  {
    invoice: "INV001",
    status: "Paid",
    method: "Credit card",
    amount: "$250.00",
  },
  { invoice: "INV002", status: "Pending", method: "PayPal", amount: "$150.00" },
  {
    invoice: "INV003",
    status: "Unpaid",
    method: "Bank transfer",
    amount: "$350.00",
  },
  {
    invoice: "INV004",
    status: "Paid",
    method: "Credit card",
    amount: "$450.00",
  },
  { invoice: "INV005", status: "Paid", method: "PayPal", amount: "$550.00" },
  {
    invoice: "INV006",
    status: "Pending",
    method: "Bank transfer",
    amount: "$200.00",
  },
  {
    invoice: "INV007",
    status: "Unpaid",
    method: "Credit card",
    amount: "$300.00",
  },
]

function StatusBadge({ status }: { status: string }) {
  if (status === "Paid") {
    return <Badge className="bg-success/15 text-success">Paid</Badge>
  }
  if (status === "Unpaid") {
    return <Badge variant="destructive">Unpaid</Badge>
  }
  return <Badge variant="secondary">{status}</Badge>
}

export function TableDemo() {
  return (
    <Table>
      <TableCaption>A list of your recent invoices.</TableCaption>
      <TableHeader>
        <TableRow>
          <TableHead className="w-[100px]">Invoice</TableHead>
          <TableHead>Status</TableHead>
          <TableHead>Method</TableHead>
          <TableHead className="text-right">Amount</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {invoices.map((invoice) => (
          <TableRow key={invoice.invoice}>
            <TableCell className="font-medium">{invoice.invoice}</TableCell>
            <TableCell>
              <StatusBadge status={invoice.status} />
            </TableCell>
            <TableCell>{invoice.method}</TableCell>
            <TableCell className="text-right">{invoice.amount}</TableCell>
          </TableRow>
        ))}
      </TableBody>
      <TableFooter>
        <TableRow>
          <TableCell colSpan={3}>Total</TableCell>
          <TableCell className="text-right">$2,250.00</TableCell>
        </TableRow>
      </TableFooter>
    </Table>
  )
}

Installation

pnpm dlx shadcn@latest add https://ui.ikramhasan.com/r/table.json

Usage

import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"
<Table>
  <TableCaption>A list of your recent invoices.</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead className="w-[100px]">Invoice</TableHead>
      <TableHead>Status</TableHead>
      <TableHead>Method</TableHead>
      <TableHead className="text-right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell className="font-medium">INV001</TableCell>
      <TableCell>Paid</TableCell>
      <TableCell>Credit card</TableCell>
      <TableCell className="text-right">$250.00</TableCell>
    </TableRow>
  </TableBody>
</Table>

The table is its own frame: the header, footer and caption sit on a muted shell, and the body rows on a raised card inside it. Don't wrap it in another bordered container.

Examples

Actions

A row action menu with a DropdownMenu. The row keeps its hover fill while the menu is open.

ProductPriceActions
Wireless mouse$29.99
Mechanical keyboard$129.99
USB-C hub$49.99
"use client"

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 TableActions() {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Product</TableHead>
          <TableHead>Price</TableHead>
          <TableHead className="text-right">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="py-0 text-right">
              <DropdownMenu>
                <DropdownMenuTrigger
                  render={
                    <Button
                      variant="ghost"
                      size="icon-sm"
                      className="-mr-1.5"
                    />
                  }
                >
                  <MoreHorizontalIcon />
                  <span className="sr-only">Open menu</span>
                </DropdownMenuTrigger>
                <DropdownMenuContent align="end">
                  <DropdownMenuItem>Edit</DropdownMenuItem>
                  <DropdownMenuItem>Duplicate</DropdownMenuItem>
                  <DropdownMenuSeparator />
                  <DropdownMenuItem variant="destructive">
                    Delete
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}

Selection

Set data-state="selected" on a TableRow to tint it.

NameEmailRole
Olivia Martinolivia@example.comOwner
Jackson Leejackson@example.comAdmin
Isabella Nguyenisabella@example.comMember
William Kimwill@example.comMember
"use client"

import * as React from "react"

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

const members = [
  {
    id: "1",
    name: "Olivia Martin",
    email: "olivia@example.com",
    role: "Owner",
  },
  { id: "2", name: "Jackson Lee", email: "jackson@example.com", role: "Admin" },
  {
    id: "3",
    name: "Isabella Nguyen",
    email: "isabella@example.com",
    role: "Member",
  },
  { id: "4", name: "William Kim", email: "will@example.com", role: "Member" },
]

export function TableSelection() {
  const [selected, setSelected] = React.useState<string[]>(["2"])

  const allSelected = selected.length === members.length

  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead className="w-8">
            <Checkbox
              checked={allSelected}
              onCheckedChange={(checked) =>
                setSelected(checked ? members.map((member) => member.id) : [])
              }
              aria-label="Select all"
            />
          </TableHead>
          <TableHead>Name</TableHead>
          <TableHead>Email</TableHead>
          <TableHead className="text-right">Role</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {members.map((member) => {
          const isSelected = selected.includes(member.id)
          return (
            <TableRow
              key={member.id}
              data-state={isSelected ? "selected" : undefined}
            >
              <TableCell>
                <Checkbox
                  checked={isSelected}
                  onCheckedChange={(checked) =>
                    setSelected((current) =>
                      checked
                        ? [...current, member.id]
                        : current.filter((id) => id !== member.id)
                    )
                  }
                  aria-label={`Select ${member.name}`}
                />
              </TableCell>
              <TableCell className="font-medium">{member.name}</TableCell>
              <TableCell className="text-muted-foreground">
                {member.email}
              </TableCell>
              <TableCell className="text-right">
                <Badge
                  variant={member.role === "Owner" ? "default" : "secondary"}
                >
                  {member.role}
                </Badge>
              </TableCell>
            </TableRow>
          )
        })}
      </TableBody>
    </Table>
  )
}

Key/value

Without a TableHeader, the card fills the shell.

StatusConnected
Products synced1,284
Last sync2 minutes ago
Connected onMar 14, 2026
import {
  CalendarIcon,
  PackageIcon,
  RefreshCwIcon,
  ShieldCheckIcon,
} from "lucide-react"

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

const details = [
  { icon: ShieldCheckIcon, label: "Status", value: "Connected" },
  { icon: PackageIcon, label: "Products synced", value: "1,284" },
  { icon: RefreshCwIcon, label: "Last sync", value: "2 minutes ago" },
  { icon: CalendarIcon, label: "Connected on", value: "Mar 14, 2026" },
]

export function TableKeyValue() {
  return (
    <div className="w-full max-w-md">
      <Table className="table-fixed">
        <TableBody>
          {details.map(({ icon: Icon, label, value }) => (
            <TableRow key={label}>
              <TableCell className="border-r text-muted-foreground">
                <span className="flex items-center gap-2 [&>svg]:size-4">
                  <Icon />
                  {label}
                </span>
              </TableCell>
              <TableCell>
                {label === "Status" ? (
                  <Badge className="bg-success/15 text-success">{value}</Badge>
                ) : (
                  value
                )}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  )
}

Data Table

For sorting, filtering and pagination, build a data table with TanStack Table on top of these parts. See the Data Table guide.

API Reference

Each part renders the HTML element of the same name and accepts all of its props: Table (table, inside a scrolling div with data-slot="table-container"), TableHeader (thead), TableBody (tbody), TableFooter (tfoot), TableRow (tr), TableHead (th), TableCell (td) and TableCaption (caption).

AttributePartDescription
data-state="selected"TableRowTints the row as selected.