Table

Shows large data sets that people sort, select and resize.

10,000 payments · drag headers to reorder, edges to resize

Installation

pnpm dlx shadcn@latest add @paneui/table

Usage

Examples

Basic

Loading and Empty

Use while the first rows load or when a filter returns nothing.

Row Click and Density

Scroll to Row

Use to jump to a search hit or a newly added row in a long list.

Scrollbars

The table scrolls in a Scroll Area. The vertical track starts below the sticky header and both tracks stay inside the rounded frame.

Keyboard

Sortable headers are buttons (asc → desc → off). With reorderable, Alt + ← / → moves the focused column; with resizable, focus a column edge and use ← / → (Shift for bigger steps), or double-click it to reset. Shift-click a checkbox to select a range.

Table Editable and Table Async build on this core.

API Reference

Table

Virtualized data table. Renders a div tree with table / grid roles; no other DOM props pass through.

data*
readonly T[]
Rows.
columns*
readonly TableColumn<T>[]
Column definitions.
getRowId
(row: T, index: number) => string
Stable id per row; keeps selection and animation correct across sorts. Defaults to the index.
aria-label
string
Accessible name.
role= "table"
"table" | "grid"
grid for interactive, spreadsheet-style cells.
selectable= false
boolean
Add a checkbox lane; shift-click selects ranges.
selectedRowIds
readonly string[]
Controlled selection.
defaultSelectedRowIds
readonly string[]
Initial selection.
onSelectionChange
(ids: string[]) => void
Called when the selection changes.
sort
{ key: string; direction: "asc" | "desc" } | null
Controlled sort.
defaultSort= null
{ key: string; direction: "asc" | "desc" } | null
Initial sort.
onSortChange
(sort: SortState) => void
Called when a header cycles the sort.
resizable= false
boolean
Drag column edges to resize.
minColumnWidth= 72
number
Resize floor in px.
onColumnResize
(key: string, width: number) => void
Called after a resize.
reorderable= false
boolean
Drag headers to reorder.
columnOrder
readonly string[]
Controlled column order (keys).
defaultColumnOrder
readonly string[]
Initial column order.
onColumnOrderChange
(keys: string[]) => void
Called after a reorder.
rowHeight= 44
number
Row height in px.
headerHeight= 40
number
Header height in px.
height= 440
number
Viewport height in px.
overscan= 8
number
Rows rendered beyond the viewport; rows further out are not rendered.
loading= false
boolean
Skeleton rows when there is no data yet, a loading footer otherwise; pauses onEndReached.
skeletonRows
number
Number of skeleton rows while loading with no data.
onEndReached
() => void
Called when the list nears its end.
endReachedThreshold= 8
number
How many rows from the end onEndReached fires.
footer
ReactNode
A band under the last row (load more, add row, end of list).
emptyState= "No rows"
ReactNode
Shown when there are no rows and loading is off.
renderCell
(ctx: TableCellContext<T> & { content: ReactNode }) => ReactNode
Wrap or replace any cell; content is what the table would have rendered.
renderHeader
(ctx: TableHeaderContext<T>) => ReactNode
Replace a header label; spread labelProps onto your element.
headerAccessory
(ctx: TableHeaderContext<T>) => ReactNode
Extra content inside a header cell, e.g. a column menu.
gutter
{ width: number; header?: ReactNode; cell: (ctx: TableRowContext<T>) => ReactNode }
Leading lane before the checkbox, e.g. row handles.
onRowClick
(ctx: TableRowContext<T>, event: MouseEvent) => void
Called when a row is clicked.
rowClassName
(ctx: TableRowContext<T>) => string | undefined
Class per row from its context, e.g. text-muted-foreground for archived rows. Tokens only.
animateLayout= true
boolean
Slide rows and columns into new positions on insert, delete, sort and reorder. Off under reduced motion.
className
string
Class for the outer surface.
ref
Ref<TableHandle>
scrollToIndex(index, { align, behavior }), scrollToTop(behavior) and element().
PropTypeDefaultDescription
data*readonly T[]—Rows.
columns*readonly TableColumn<T>[]—Column definitions.
getRowId(row: T, index: number) => string—Stable id per row; keeps selection and animation correct across sorts. Defaults to the index.
aria-labelstring—Accessible name.
role"table" | "grid""table"grid for interactive, spreadsheet-style cells.
selectablebooleanfalseAdd a checkbox lane; shift-click selects ranges.
selectedRowIdsreadonly string[]—Controlled selection.
defaultSelectedRowIdsreadonly string[]—Initial selection.
onSelectionChange(ids: string[]) => void—Called when the selection changes.
sort{ key: string; direction: "asc" | "desc" } | null—Controlled sort.
defaultSort{ key: string; direction: "asc" | "desc" } | nullnullInitial sort.
onSortChange(sort: SortState) => void—Called when a header cycles the sort.
resizablebooleanfalseDrag column edges to resize.
minColumnWidthnumber72Resize floor in px.
onColumnResize(key: string, width: number) => void—Called after a resize.
reorderablebooleanfalseDrag headers to reorder.
columnOrderreadonly string[]—Controlled column order (keys).
defaultColumnOrderreadonly string[]—Initial column order.
onColumnOrderChange(keys: string[]) => void—Called after a reorder.
rowHeightnumber44Row height in px.
headerHeightnumber40Header height in px.
heightnumber440Viewport height in px.
overscannumber8Rows rendered beyond the viewport; rows further out are not rendered.
loadingbooleanfalseSkeleton rows when there is no data yet, a loading footer otherwise; pauses onEndReached.
skeletonRowsnumber—Number of skeleton rows while loading with no data.
onEndReached() => void—Called when the list nears its end.
endReachedThresholdnumber8How many rows from the end onEndReached fires.
footerReactNode—A band under the last row (load more, add row, end of list).
emptyStateReactNode"No rows"Shown when there are no rows and loading is off.
renderCell(ctx: TableCellContext<T> & { content: ReactNode }) => ReactNode—Wrap or replace any cell; content is what the table would have rendered.
renderHeader(ctx: TableHeaderContext<T>) => ReactNode—Replace a header label; spread labelProps onto your element.
headerAccessory(ctx: TableHeaderContext<T>) => ReactNode—Extra content inside a header cell, e.g. a column menu.
gutter{ width: number; header?: ReactNode; cell: (ctx: TableRowContext<T>) => ReactNode }—Leading lane before the checkbox, e.g. row handles.
onRowClick(ctx: TableRowContext<T>, event: MouseEvent) => void—Called when a row is clicked.
rowClassName(ctx: TableRowContext<T>) => string | undefined—Class per row from its context, e.g. text-muted-foreground for archived rows. Tokens only.
animateLayoutbooleantrueSlide rows and columns into new positions on insert, delete, sort and reorder. Off under reduced motion.
classNamestring—Class for the outer surface.
refRef<TableHandle>—scrollToIndex(index, { align, behavior }), scrollToTop(behavior) and element().
data-selected
On selected rows.
data-row-id
The row id, on each row.
data-col-key
The column key, on header and body cells.
data-dragging
On a header while it is being dragged.
data-resizing
On a resize handle while it is being dragged.
Data attributeDescription
data-selectedOn selected rows.
data-row-idThe row id, on each row.
data-col-keyThe column key, on header and body cells.
data-draggingOn a header while it is being dragged.
data-resizingOn a resize handle while it is being dragged.

TableColumn

Type of each entry in columns.

key*
string
Stable key; also the property read from the row when there is no accessor.
header*
ReactNode
Header label.
width= 160
number
Width in px.
minWidth
number
Floor for resizing. Falls back to minColumnWidth.
grow
boolean
Soak up leftover width (until resized).
align= "left"
"left" | "center" | "right"
Cell alignment.
sortable
boolean
Make the header sort on click.
resizable
boolean
Set false to opt out of resizing.
accessor
(row: T) => unknown
Raw value for the cell, sorting and default rendering.
sortValue
(row: T) => string | number | null | undefined
Value used for sorting when it differs from the accessor.
cell
(row: T, ctx: TableCellContext<T>) => ReactNode
Custom cell content.
className
string
Class for body cells.
headerClassName
string
Class for the header cell.
PropTypeDefaultDescription
key*string—Stable key; also the property read from the row when there is no accessor.
header*ReactNode—Header label.
widthnumber160Width in px.
minWidthnumber—Floor for resizing. Falls back to minColumnWidth.
growboolean—Soak up leftover width (until resized).
align"left" | "center" | "right""left"Cell alignment.
sortableboolean—Make the header sort on click.
resizableboolean—Set false to opt out of resizing.
accessor(row: T) => unknown—Raw value for the cell, sorting and default rendering.
sortValue(row: T) => string | number | null | undefined—Value used for sorting when it differs from the accessor.
cell(row: T, ctx: TableCellContext<T>) => ReactNode—Custom cell content.
classNamestring—Class for body cells.
headerClassNamestring—Class for the header cell.

TableSkeletonRow

Placeholder row for custom loading states.

index*
number
Row index; varies the bar widths.
cells*
readonly number[]
Cell widths in px, in display order.
height*
number
Row height in px.
className
string
Class for the row.
PropTypeDefaultDescription
index*number—Row index; varies the bar widths.
cells*readonly number[]—Cell widths in px, in display order.
height*number—Row height in px.
classNamestring—Class for the row.

TableLoadingFooter

Default “Loading more…” band with a spinner, for footer.

label= "Loading more…"
ReactNode
Text beside the spinner.
PropTypeDefaultDescription
labelReactNode"Loading more…"Text beside the spinner.

TableSortIndicator

The header sort chevron, for custom renderHeader labels.

direction*
"asc" | "desc" | null
Current sort of the column.
PropTypeDefaultDescription
direction*"asc" | "desc" | null—Current sort of the column.

useControllableState

const [value, setValue] = useControllableState(value, defaultValue, onChange) gives the table family its controlled-or-uncontrolled state. tableHeaderLabelClass and compareTableValues are exported too.