Skip to content

Table

Shows rows of data in columns, read-only. For sorting, selection and row actions, use Data table.

Library
What’s this?
Open opportunities this quarter.
Opportunity nameAccount nameStageAmount
Acme - 1,200 WidgetsAcmeProspecting$140,000
Global Media - 400 LicensesGlobal MediaNegotiation$62,500
Edge Communications - UpgradeEdge CommunicationsClosed Won$27,000
Burlington Textiles - 200 SeatsBurlington TextilesProposal$235,000
Total$464,500

Installation

pnpm dlx shadcn@latest add @wisty/table

The CLI installs the Radix or Base UI file to match your project.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • People read and compare records in rows and columns, like a related list or a report.
  • The data is read-only, or each row has a link or a simple action.

Don't use it

  • People sort, select rows for a bulk action, resize columns, use row actions or load more rows, and you want that handled for you. Use Data Table.
  • People edit cells in place or move between cells with the arrow keys. Neither Table nor Data Table does that yet.
  • The data is one record's fields. Use a definition list or a Card.
  • The content is a layout, not data.

Usage

Table
├── TableCaption
├── TableHeader
│   └── TableRow
│       └── TableHead
├── TableBody
│   └── TableRow
│       └── TableCell
└── TableFooter
    └── TableRow
        └── TableCell
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"
<Table aria-label="Contacts">
  <TableHeader>
    <TableRow>
      <TableHead>Name</TableHead>
      <TableHead>Email</TableHead>
    </TableRow>
  </TableHeader>
  <TableBody>
    <TableRow>
      <TableCell>Ada Lovelace</TableCell>
      <TableCell>ada@acme.example</TableCell>
    </TableRow>
  </TableBody>
</Table>

Two patterns come from attributes. A Checkbox straight inside the first TableHead and each row's first TableCell makes a selection column, and data-state="selected" on a TableRow draws it selected, as the Selection column example shows. A TableHead with aria-sort and a plain <button> inside becomes a sortable header: give the button an ArrowDownIcon, which turns for ascending and hides for none. The Sortable headers example sorts the rows itself.

<TableHead aria-sort="ascending">
  <button type="button">
    Name
    <ArrowDownIcon />
  </button>
</TableHead>

Examples

Selection column

A checkbox in each row's first cell and one in the header, and the checked row is drawn selected with `data-state="selected"` on `TableRow`.

NameTitleEmail
Ada LovelaceChief Analystada@acme.example
Grace HopperVP Engineeringgrace@acme.example
Alan TuringHead of Researchalan@acme.example

Sortable headers

Headers with `aria-sort` and a `<button>`, sorted by name. Click a header to sort by it.

Ada LovelaceChief Analystada@acme.example
Alan TuringHead of Researchalan@acme.example
Grace HopperVP Engineeringgrace@acme.example

API reference

SLDS mapping

Table is slds-table slds-table_bordered as lightning-datatable draws it, so it matches Data Table: no slds-table_cell-buffer, and a line under the header. TableHeader cells are the header row, TableRow takes slds-is-selected from data-state="selected", a cell holding a checkbox is lightning-datatable's selection column, and a TableHead with aria-sort is slds-is-sortable with its button as slds-th__action and its arrow as slds-is-sortable__icon. TableFooter and TableCaption have no SLDS counterpart: the footer borrows the header's look, the caption is weak text under the table.

Known gaps:

  • Striping, fixed layout, column borders, resizing and inline editing aren't styled. Data Table adds a fixed layout and resizable columns.
  • The header checkbox can show SLDS's mixed state: pass checked="indeterminate" in Radix projects and indeterminate in Base UI projects.

Accessibility

  • Name the table with a TableCaption or an aria-label on Table.
  • Put column headers in TableHeader. For a row header, use TableHead with scope="row" in a body row.
  • data-state="selected" only changes the look. Select rows with checkboxes, named by their row ("Select Ada Lovelace") and "Select all rows" in the header, so the state reaches assistive tech.
  • Keep aria-sort on every sortable header, with "none" on the unsorted ones, so screen readers hear which column is sorted and how. The button inside names the column.

Responsive behavior

  • The table fills its container. When the columns don't fit, the container scrolls sideways; the page doesn't.
  • Cells don't wrap. Add whitespace-normal to a cell to let long text wrap.

Performance

  • Every row renders. For more than a few hundred rows, page the data with Pagination, or use Data Table, which can render only the rows in view.

Rules

  • Set data-state="selected" for a selected row. A background class pins one theme's color.
  • Put Checkbox directly in the cell, not in a wrapper, or a row's box no longer sits on the text line.
  • Put a plain <button> in a sortable header, never Button: its own height, padding and hover fight the header's.
  • Align numbers with text-end on their head and cells.
  • Don't add borders, stripes or padding classes; the tokens carry each theme's table.