Navigation and layout

Table

A presentational semantic table with two densities, a pinned header, grid lines, a composable caption, and a loading state.

⠋

Install

npx shadcn@latest add https://4af.selfsimilar.dev/r/table.json

Usage

import {
  Table,
  TableBody,
  TableCaption,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";

<Table>
  <TableCaption>People</TableCaption>
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Role</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    {people.map((person) => (
      <TableRow key={person.id}>
        <TableCell>{person.name}</TableCell>
        <TableCell>{person.role}</TableCell>
      </TableRow>
    ))}
  </TableBody>
</Table>

API

Main exports: Table, TableCaption, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell

Native <table> semantics with the library's row rules, two densities, a pinned header, optional grid lines, an optional caption, and a loading state.

This table has no controls and no state. For a checkbox column, a selection action row, column sorting, or a per-row actions menu, install data-table, which composes these parts.

Props

Table

PropTypeDefaultDescription
size"sm" | "md""sm"Row density.
borderedbooleanfalseFrame around the table and a rule between every pair of columns. Row rules are always drawn.
stickyHeaderbooleantruePins the header row to the top of the scroll container.
loadingbooleanfalseFill the body with placeholder rows and mark the table busy.
loadingRowsnumber5How many placeholder rows to draw.
wrapperClassNamestring—Class for the scroll container.
rowComponentComponentTypeTableRowThe component placeholder rows are built from. DataTable sets this; you should not need to.

The other parts take their native element's props and add nothing of their own.

sm is 36px rows, md is 44px; both use a 36px header.

Caption

TableCaption must be the first child, as a native <caption> must be. The visible caption is centered below the scrolling table while the native one stays available to assistive technology.

<Table>
  <TableCaption>Release history</TableCaption>
  <TableHeader>{/* ... */}</TableHeader>
</Table>

Loading

loading replaces the body's rows with placeholders and sets aria-busy. The header keeps its labels. Placeholder rows take their shape from the header you wrote, so give the header the columns you intend to render.

<Table loading={isLoading} loadingRows={8}>

Layout

The table sits in a scroll container carrying the library's thin scrollbar; style it with wrapperClassName. With bordered, the frame is drawn on that container.

The header needs a bounded height to pin to:

<Table wrapperClassName="max-h-80">

Pinned header cells take bg-background. Pass another fill through className on TableHead when the table sits on a raised surface.

Rules

Boundaries — under the header, over the footer, and the frame when bordered — are 1.5px at the full border token. Separators between rows and columns are 1px at 60% of it.

A vertical rule belongs to the leading edge of the cell that starts the column, which is how a column declines its own rule: data-table's checkbox and actions columns draw none.

Composing on top

useTableContext() returns size, bordered, stickyHeader, loading, loadingRows and columns. useTableSection() returns "header" | "body" | "footer" — a row can't infer this, since a <thead> and a <tbody> hold identical <tr>s.

With rowComponent, these are what data-table builds on. Reach for them if you are adding a column of your own.