Skip to content

ComponentsCollections

Table

An accessible data table with row selection and automatic checkbox column, sortable columns, row actions and links, dynamic columns, drag-to-reorder, empty states, and three densities with optional striping, grid lines and frame.

React AriaSource
InvoiceCustomerStatusAmount
INV-1042Acme CorpPaid$2,500.00
INV-1043GlobexPending$1,150.00
INV-1044InitechOverdue$3,420.00
INV-1045UmbrellaPaid$980.00

Installation

pnpm dlx shadcn@latest add @desyne/table

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import {
  Cell,
  Column,
  Row,
  Table,
  TableBody,
  TableHeader,
} from "@/components/ui/table";
tsx
<Table aria-label="Users">
  <TableHeader>
    <Column isRowHeader>Name</Column>
    <Column>Role</Column>
  </TableHeader>
  <TableBody items={users}>
    {(user) => (
      <Row>
        <Cell>{user.name}</Cell>
        <Cell>{user.role}</Cell>
      </Row>
    )}
  </TableBody>
</Table>

Mark a row header

Set isRowHeader on the column that identifies each row (usually a name or id). Screen readers read it when moving between rows, so users know which row they're in. Every item in items also needs a unique id.

When to use

  • Table — comparing records across several attributes, with sorting, selection or bulk actions.
  • Grid List — a single column of rich rows, cards, or mobile-first lists.
  • List Box — picking values, not browsing data.
  • For simple, non-interactive tabular content in prose, a plain HTML <table> is fine.

Anatomy

tsx
<Table>                     {/* scroll container + role="grid" */}
  <TableHeader>             {/* adds a select-all column when selecting */}
    <Column isRowHeader />
    <Column allowsSorting />
  </TableHeader>
  <TableBody>
    <Row>                   {/* adds a checkbox cell when selecting */}
      <Cell />
      <Cell />
    </Row>
  </TableBody>
</Table>
PartRendersNotes
Table<div> › <table role="grid">The div scrolls horizontally and draws the card frame (framed). Provides density, striped and bordered to every cell.
TableHeader<thead>Muted background. Adds a checkbox column (select all in multiple mode) when selectionBehavior is toggle.
Column<th role="columnheader">Small, muted, non-wrapping. Shows a sort arrow when allowsSorting.
TableBody<tbody>Rows, or renderEmptyState when there are none.
Row<tr role="row">Hover and selected backgrounds. Adds a checkbox cell when selection uses checkboxes.
Cell<td role="gridcell">Non-wrapping, padded by density.

Examples

Density, striped and bordered

density sets cell padding (compact, default, comfortable). striped shades every other row, and bordered adds vertical grid lines.

HostRegionCPU
api-prod-01us-east-142%
api-prod-02us-east-138%
worker-01eu-central-171%
worker-02eu-central-166%

Without a frame

framed={false} drops the table's own border and shadow so it can sit inside a card or panel.

Plan limits

LimitFreePro
API requests10k / mo1M / mo
Seats3Unlimited
Storage1 GB100 GB
Log retention7 days90 days

Single selection

selectionMode="single" adds a checkbox column without a select-all box. Combine with disallowEmptySelection for a required choice.

PlanCPUMemoryPrice
Starter1 vCPU1 GB$5 / mo
Standard2 vCPU4 GB$24 / mo
Performance4 vCPU8 GB$48 / mo
Memory optimized4 vCPU32 GB$96 / mo

Selected: Standard

Multiple selection and sorting

selectionMode="multiple" adds a select-all checkbox to the header. Sorting is controlled: give sortable columns an id and allowsSorting, then sort your data in onSortChange.

1 selected

NameRole
IN
Isabella Nguyenisabella@acme.com
Owner
JL
Jackson Leejackson@acme.com
Member
OM
Olivia Martinolivia@acme.com
Admin
WK
William Kimwill@acme.com
Member

Sorting

Clicking a sortable header (or pressing Enter on it) toggles ascending and descending. The table only reports the new SortDescriptor; compare numbers numerically and strings with localeCompare (or Intl.Collator) yourself.

CountryCapitalPopulation (M)Area (km²)
BrazilBrasília216.48,515,767
JapanTokyo124.5377,975
GermanyBerlin84.5357,588
KenyaNairobi55.1580,367
CanadaOttawa40.19,984,670
NepalKathmandu30.9147,516

Selection behavior

selectionBehavior="replace" removes the checkbox column. A click selects one row, ⌘/Ctrl and Shift add more, and arrow keys move the selection like a desktop mail client.

FromSubjectDate
StripeYour October payout is on its wayOct 2
LinearWeekly digest: 14 issues closedOct 1
VercelDeployment succeeded for web-appSep 30
GitHub[acme/api] PR #482 approvedSep 30
FigmaAna shared “Checkout v3” with youSep 29

Click selects a row. ⌘/Ctrl or Shift to select more.

Disabled rows

disabledKeys prevents selecting (and by default focusing) rows. Style them with data-disabled: on Row. disabledBehavior="selection" keeps them focusable.

NameKeyStatus
Productionsk_live_4f…Active
Stagingsk_test_91…Active
Legacy integrationsk_live_0a…Revoked
CI pipelinesk_test_7c…Active

Row actions

onRowAction makes rows actionable: clicking a row or pressing Enter calls it with the row's key. With checkbox selection as well, clicking a row performs the action while nothing is selected, and toggles selection once rows are selected; use selectionBehavior="replace" to select on click and act on double click or Enter.

CommitBranchEnvironmentAge
Fix checkout tax roundingmainProduction4m
Add SAML metadata endpointfeat/ssoPreview38m
Bump dependenciesmainProduction2h
Keep focus in popoverfix/date-pickerPreview5h

Click a row or press Enter to open a deployment.

Give a Row an href and the whole row navigates. Works with client-side routers through React Aria's RouterProvider.

ComponentCategoryParts
ButtonActions1
SelectForms4
DialogOverlays9
TableCollections6

Dynamic columns

Pass columns to TableHeader and each Row, and render both with functions. Here a multi-select Menu toggles column visibility; dependencies on TableBody re-renders rows when the columns change.

CustomerPlanSeatsMRR
NorthwindEnterprise240$18,400
FabrikamBusiness56$3,920
TailspinPro12$480
ContosoBusiness88$6,160

Rich cells

Cells take any content: avatars, badges, progress bars. Right-align numbers with text-right tabular-nums on both the column and its cells.

ProjectOwnerStatusProgressDue
Checkout v3ASAna SouzaOn track72%Oct 18
SSO for enterpriseKWKenji WatanabeAt risk41%Oct 31
Usage-based billingMPMaya PatelBlocked18%Nov 14
Docs searchLFLeo FischerDone100%Sep 26

Empty state

renderEmptyState on TableBody fills the body when there are no rows.

AlertSeverityTime
No alerts match your filters.

Drag to reorder

Pass dragAndDropHooks from useDragAndDrop, add a column for the handle, and render a React Aria Button with slot="drag" in its cell. Rows can then be reordered by mouse, touch or keyboard.

ReorderStepChannelNotify
1EmailImmediately
2Slack #on-callAfter 5 min
3SMSAfter 10 min
4Phone callAfter 15 min

Without grid lines

divided={false} removes the lines between rows. Pair it with striped (or nothing at all) for light, list-like tables such as plan comparisons.

PlanSeatsStoragePer seat / month
Starter310 GB$0
Team25250 GB$12
Business1002 TB$24
EnterpriseUnlimitedCustomTalk to us

Bleed

bleed drops the frame and lets the table run into its parent's horizontal padding (-mx-4, sm:-mx-6), so row hovers and dividers reach the card's edges while the first and last columns stay aligned with the text around them. Use it inside cards and sheets that use px-4 sm:px-6.

Members

The table runs edge to edge; its first and last columns line up with this text.

NameRoleLast active
MMaya Chenmaya@fernhill.studioOwner2 min ago
TTomás Riveratomas@fernhill.studioAdmin1 h ago
HHana Kobayashihana@fernhill.studioEditorYesterday
AArjun Mehtaarjun@fernhill.studioViewerSep 28

Expandable rows

Keep a set of expanded ids, put a TableExpandButton in the first cell and render the detail as an extra Row right after the parent, with one Cell that spans every column (colSpan). The button sets aria-expanded, and the detail row stays in the grid, so keyboard users reach it with the arrow keys.

OrderCustomerStatusTotal
ORD-4821Fernhill StudioShipped$1,284.00
  • DSK-OAK-140Oak desk, 140 cm1 × $940.00$940.00
  • LMP-BRS-02Brass task lamp2 × $172.00$344.00
ORD-4820Halcyon HealthProcessing$2,310.00
ORD-4819Copperleaf FoodsDelivered$486.00

Infinite scrolling

Give the table a fixed-height scroll container with containerClassName (the header stays sticky) and add TableLoadMore as the last child of TableBody. It calls onLoadMore when it scrolls into view and shows a spinner row while isLoading is true. Remove it, or stop calling your loader, when there's nothing left to fetch.

Time (UTC)ServiceLevelMessage
14:00:00apiinfoRequest completed
13:59:23webwarnJob enqueued
13:58:46workerinfoToken refreshed
13:58:09billingerrorSlow query (412 ms)
13:57:32searchinfoRetrying webhook delivery
13:56:55authinfoCache miss, fetched from origin
13:56:18apiwarnRequest completed
13:55:41webinfoJob enqueued
13:55:04workererrorToken refreshed
13:54:27billinginfoSlow query (412 ms)
13:53:50searchinfoRetrying webhook delivery
13:53:13authwarnCache miss, fetched from origin
13:52:36apiinfoRequest completed
13:51:59weberrorJob enqueued
13:51:22workerinfoToken refreshed
13:50:45billinginfoSlow query (412 ms)
13:50:08searchwarnRetrying webhook delivery
13:49:31authinfoCache miss, fetched from origin
13:48:54apierrorRequest completed
13:48:17webinfoJob enqueued

20 of 120 events

Resizable columns

Set allowsResizing on Table to let people drag column edges. Each Column gets a resize handle; give columns a defaultWidth (pixels, % or fr) and a minWidth/maxWidth to shape the starting layout, and opt one out with isResizable={false}. Keyboard users focus a header, press Enter to enter resize mode and use the arrow keys. onResizeEnd receives every column's width, which is the place to persist a user's layout. Cells truncate with an ellipsis while the table is resizable.

NameOwnerTypeSizeUpdated
Q3 board deck — final review.pdfMaya ChenPDF4.2 MBOct 2, 2026
Brand guidelines 2027.figTomás RiveraFigma18.6 MBSep 30, 2026
Customer interviews — synthesis notes.docxHana KobayashiDoc0.8 MBSep 28, 2026
Pricing model v4.xlsxArjun MehtaSheet1.3 MBSep 27, 2026
Launch video — 30s cut.mp4Lena FischerVideo212.4 MBSep 21, 2026

Drag a column edge, or focus a header and press Enter to resize with the arrow keys.

Recipes

Data table

Search, a status filter, sortable columns, a bulk-action bar that replaces the toolbar while rows are selected, and client-side pagination with Paginator.

InvoiceCustomerStatusIssuedAmount
INV-1040Acme CorpPaid2026-09-28$180.00
INV-1041SoylentRefunded2026-09-25$3,899.00
INV-1042HooliPaid2026-09-22$3,418.00
INV-1043GlobexOverdue2026-09-19$2,937.00
INV-1044TyrellPending2026-09-16$2,456.00
INV-1045Stark IndustriesPaid2026-09-13$1,975.00
23 invoices

Member management

An inline role Select and a per-row action Menu. Controls inside cells are reachable with the arrow keys and labelled with the member's name.

MemberRoleLast activeActions
OMOlivia Martinolivia@acme.com
Now
JLJackson Leejackson@acme.com
2h ago
INIsabella Nguyenisabella@acme.com
Yesterday
WKWilliam Kimwill@acme.com
Sep 12

Permission matrix

A Checkbox in every cell, bordered grid lines and compact density. Each checkbox's aria-label names both the permission and the role.

PermissionViewerEditorAdmin
View projects
Comment
Edit content
Publish
Manage billing

Accessibility

  • The table is a role="grid": users move between cells with the arrow keys, and focus is a single tab stop.
  • Label the table with aria-label or aria-labelledby, and mark one column isRowHeader.
  • Sortable columns announce their sort direction with aria-sort.
  • The selection checkboxes are labelled automatically ("Select", "Select all") and linked to the row header.
  • Controls inside cells (buttons, selects, checkboxes) are reachable by arrowing into the cell. Give icon-only buttons an aria-label that names the row.
  • Visually hidden column labels (like "Actions") keep header cells meaningful for screen readers.
  • Drag and drop has keyboard and screen reader support built in.

Keyboard

KeyAction
TabMoves focus into and out of the table
↑ / ↓Moves focus to the row or cell above / below
← / →Moves focus between cells in a row (and into a cell's controls)
Home / EndMoves focus to the first / last cell in the row
Ctrl+Home / Ctrl+EndMoves focus to the first / last row
Page Up / Page DownMoves focus by a page
SpaceToggles selection of the focused row
EnterSorts by the focused column, or triggers the row action / link
Shift+↑ / Shift+↓Extends the selection (multiple)
⌘/Ctrl+ASelects all rows (multiple)
EscClears the selection

Styling

Data attributes

AttributeOnPresent when
data-focused / data-focus-visibleTable, Column, Row, CellFocused / keyboard focused
data-drop-targetTable, TableBody, RowA drag is over it
data-hoveredTableHeader, Column, Row, CellHovered with a mouse
data-pressedColumn, Row, CellBeing pressed
data-allows-sortingColumnallowsSorting is set
data-sort-directionColumnascending or descending when sorted by this column
data-emptyTableBodyThere are no rows
data-selectedRow, CellThe row is selected
data-disabledRow, CellThe row is disabled
data-selection-modeRowsingle or multiple
data-focus-visible-withinRowA cell or control in the row has keyboard focus
data-dragging / data-allows-draggingRowThe row is being dragged / drag and drop is enabled

Column and Row are Tailwind groups (group/column, group/row), so children can use group-data-selected/row: or group-data-hovered/column:.

Slots

data-slotElement
table-containerScroll container and frame
table<table>
table-header / table-columnHeader row and header cells
table-body / table-row / table-cellBody, rows and cells

Customizing

  • className on Table styles the <table> element (for example min-w-[640px] to force horizontal scroll). The container is always w-full overflow-auto.
  • Cells don't wrap by default. Add whitespace-normal to a Cell for long text, or max-w-* truncate to clip it.
  • Set column widths with w-* classes on Column.
  • Every part's className also accepts a function of its render props.
  • Column resizing and expandable tree rows aren't part of this component.

API Reference

Table

Prop

Type

Also accepts every prop of React Aria's Table.

TableHeader

Prop

Type

Column

Prop

Type

TableLoadMore

Prop

Type

TableExpandButton

Prop

Type

TableBody

Prop

Type

Row

Prop

Type

Cell

Prop

Type

  • Grid List — single-column interactive lists and cards.
  • Pagination — page controls for long tables.
  • Menu — row action menus and column pickers.
  • Checkbox — the selection control used in rows.