Skip to main content

Installation

npx shadcn@latest add table

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>

Component API

Table

Root table component with responsive wrapper.
function Table({ className, ...props }: React.ComponentProps<"table">)

TableHeader

Table header section.
function TableHeader({ className, ...props }: React.ComponentProps<"thead">)

TableBody

Table body section.
function TableBody({ className, ...props }: React.ComponentProps<"tbody">)

TableFooter

Table footer section.
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">)

TableRow

Table row with hover state.
function TableRow({ className, ...props }: React.ComponentProps<"tr">)

TableHead

Table header cell.
function TableHead({ className, ...props }: React.ComponentProps<"th">)

TableCell

Table data cell.
function TableCell({ className, ...props }: React.ComponentProps<"td">)

TableCaption

Table caption/title.
function TableCaption({ className, ...props }: React.ComponentProps<"caption">)

Examples

Basic Table

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
      <TableHead>Role</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>John Doe</TableCell>
      <TableCell>john@example.com</TableCell>
      <TableCell>Developer</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>Jane Smith</TableCell>
      <TableCell>jane@example.com</TableCell>
      <TableCell>Designer</TableCell>
    </TableRow>
  </TableBody>
</Table>
Table with footer totals:
<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Invoice</TableHead>
      <TableHead className="text-right">Amount</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>INV001</TableCell>
      <TableCell className="text-right">$250.00</TableCell>
    </TableRow>
    <TableRow>
      <TableCell>INV002</TableCell>
      <TableCell className="text-right">$150.00</TableCell>
    </TableRow>
  </TableBody>
  <TableFooter>
    <TableRow>
      <TableCell>Total</TableCell>
      <TableCell className="text-right">$400.00</TableCell>
    </TableRow>
  </TableFooter>
</Table>

With Actions

Table with action buttons:
import { MoreHorizontal } from "lucide-react"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Status</TableHead>
      <TableHead className="text-right">Actions</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {items.map((item) => (
      <TableRow key={item.id}>
        <TableCell>{item.name}</TableCell>
        <TableCell>{item.status}</TableCell>
        <TableCell className="text-right">
          <DropdownMenu>
            <DropdownMenuTrigger asChild>
              <Button variant="ghost" size="icon">
                <MoreHorizontal className="h-4 w-4" />
              </Button>
            </DropdownMenuTrigger>
            <DropdownMenuContent align="end">
              <DropdownMenuItem>Edit</DropdownMenuItem>
              <DropdownMenuItem>Delete</DropdownMenuItem>
            </DropdownMenuContent>
          </DropdownMenu>
        </TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>

Selectable Rows

Table with checkbox selection:
function SelectableTable() {
  const [selected, setSelected] = React.useState<string[]>([])

  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead className="w-[50px]">
            <Checkbox />
          </TableHead>
          <TableHead>Name</TableHead>
          <TableHead>Email</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {users.map((user) => (
          <TableRow key={user.id}>
            <TableCell>
              <Checkbox
                checked={selected.includes(user.id)}
                onCheckedChange={(checked) => {
                  setSelected(
                    checked
                      ? [...selected, user.id]
                      : selected.filter((id) => id !== user.id)
                  )
                }}
              />
            </TableCell>
            <TableCell>{user.name}</TableCell>
            <TableCell>{user.email}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}

Sortable Headers

Clickable headers for sorting:
function SortableTable() {
  const [sortBy, setSortBy] = React.useState("name")
  const [sortOrder, setSortOrder] = React.useState<"asc" | "desc">("asc")

  const handleSort = (key: string) => {
    if (sortBy === key) {
      setSortOrder(sortOrder === "asc" ? "desc" : "asc")
    } else {
      setSortBy(key)
      setSortOrder("asc")
    }
  }

  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead
            className="cursor-pointer"
            onClick={() => handleSort("name")}
          >
            Name {sortBy === "name" && (sortOrder === "asc" ? "↑" : "↓")}
          </TableHead>
          <TableHead
            className="cursor-pointer"
            onClick={() => handleSort("email")}
          >
            Email {sortBy === "email" && (sortOrder === "asc" ? "↑" : "↓")}
          </TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {/* Sorted data */}
      </TableBody>
    </Table>
  )
}

Striped Rows

Alternating row colors:
<Table>
  <TableBody>
    {items.map((item, index) => (
      <TableRow
        key={item.id}
        className={index % 2 === 0 ? "bg-muted/50" : ""}
      >
        <TableCell>{item.name}</TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>

Empty State

Show message when no data:
<Table>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {items.length === 0 ? (
      <TableRow>
        <TableCell colSpan={2} className="h-24 text-center">
          No results.
        </TableCell>
      </TableRow>
    ) : (
      items.map((item) => (
        <TableRow key={item.id}>
          <TableCell>{item.name}</TableCell>
          <TableCell>{item.email}</TableCell>
        </TableRow>
      ))
    )}
  </TableBody>
</Table>

Data Tables

For advanced data tables with sorting, filtering, and pagination, combine the Table component with @tanstack/react-table. See the Data Table documentation for a complete example.

Accessibility

  • Semantic HTML table elements
  • Proper header associations
  • Responsive overflow handling
  • Screen reader support
  • Keyboard navigation

Build docs developers (and LLMs) love