Table

A table displays data in rows and columns and enables a user to navigate and select its contents.

Name
Type
Date Modified
GamesFile folder6/7/2020
Program FilesFile folder4/7/2021
bootmgrSystem file11/20/2010
<TableContainer>
  <Table aria-label="Files">
    <TableHeader>
      <TableColumn isRowHeader>Name</TableColumn>
      <TableColumn>Type</TableColumn>
      <TableColumn>Date Modified</TableColumn>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell>Games</TableCell>
        <TableCell>File folder</TableCell>
        <TableCell>6/7/2020</TableCell>
      </TableRow>
      <TableRow>
        <TableCell>Program Files</TableCell>
        <TableCell>File folder</TableCell>
        <TableCell>4/7/2021</TableCell>
      </TableRow>
      <TableRow>
        <TableCell>bootmgr</TableCell>
        <TableCell>System file</TableCell>
        <TableCell>11/20/2010</TableCell>
      </TableRow>
    </TableBody>
  </Table>
</TableContainer>

Installation

npx shadcn@latest add @dotui/table

Usage

Use tables to display data in rows and columns with support for selection, sorting, and more.

import {
  Table,
  TableBody,
  TableCell,
  TableColumn,
  TableContainer,
  TableFooter,
  TableHeader,
  TableRow,
  TableVirtualizer,
} from '@/components/ui/table'
<TableContainer>
  <Table aria-label="Files">
    <TableHeader>
      <TableColumn isRowHeader>Name</TableColumn>
      <TableColumn>Type</TableColumn>
      <TableColumn>Date Modified</TableColumn>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell>Games</TableCell>
        <TableCell>File folder</TableCell>
        <TableCell>6/7/2020</TableCell>
      </TableRow>
    </TableBody>
    <TableFooter>
      <TableRow>
        <TableCell colSpan={2}>Total</TableCell>
        <TableCell>1 item</TableCell>
      </TableRow>
    </TableFooter>
  </Table>
</TableContainer>

Use TableContainer to control the scroll region.

<TableContainer className="max-h-80">
  <Table aria-label="Files">{/* ... */}</Table>
</TableContainer>

By default, TableContainer renders a plain div so the table uses native CSS auto layout. Set resizable to opt into React Aria's resizable table layout. In that mode, use width, defaultWidth, minWidth, and maxWidth on TableColumn.

<TableContainer resizable>
  <Table aria-label="Files">
    <TableHeader>
      <TableColumn allowsResizing defaultWidth={240}>
        Name
      </TableColumn>
      <TableColumn allowsResizing defaultWidth={120}>
        Type
      </TableColumn>
    </TableHeader>
    <TableBody>{/* ... */}</TableBody>
  </Table>
</TableContainer>

Use TableVirtualizer for large collections. It uses the table layout exported by this component so row, cell, and selection column alignment stays consistent. Row and header heights default to the active density.

<TableContainer className="h-80">
  <TableVirtualizer>
    <Table aria-label="Files">{/* ... */}</Table>
  </TableVirtualizer>
</TableContainer>

Anatomy

A Table composes a TableHeader of TableColumns with a TableBody of TableRows and TableCells, optionally a TableFooter, all inside a TableContainer scroll region. Mark one column isRowHeader so each row is labeled by that cell.

import {
  Table,
  TableBody,
  TableCell,
  TableColumn,
  TableContainer,
  TableFooter,
  TableHeader,
  TableRow,
} from '@/components/ui/table'
;<TableContainer>
  <Table>
    <TableHeader>
      <TableColumn isRowHeader>Name</TableColumn>
      <TableColumn>Type</TableColumn>
    </TableHeader>
    <TableBody>
      <TableRow>
        <TableCell>Games</TableCell>
        <TableCell>File folder</TableCell>
      </TableRow>
    </TableBody>
    <TableFooter>
      <TableRow>
        <TableCell colSpan={2}>1 item</TableCell>
      </TableRow>
    </TableFooter>
  </Table>
</TableContainer>

Content

Pass static TableRows as children, or drive the table from data by giving TableHeader and TableBody an items array with a render function. Give each TableColumn an id matching a data key, and pass the same columns to each TableRow.

<Table aria-label="Files">
  <TableHeader columns={columns}>
    {(column) => (
      <TableColumn isRowHeader={column.isRowHeader}>{column.name}</TableColumn>
    )}
  </TableHeader>
  <TableBody items={data}>
    {(item) => (
      <TableRow columns={columns}>
        {(column) => <TableCell>{item[column.id]}</TableCell>}
      </TableRow>
    )}
  </TableBody>
</Table>

Selection

Set selectionMode to single or multiple to enable row selection. Control it with selectedKeys and onSelectionChange (the value is 'all' or a Set of keys), and pass disallowEmptySelection to keep at least one row selected.

<Table
  aria-label="Files"
  selectionMode="multiple"
  selectedKeys={selected}
  onSelectionChange={setSelected}
  disallowEmptySelection
>
  {/* ... */}
</Table>

Sorting

Mark sortable columns with allowsSorting, then hold a sortDescriptor ({ column, direction }) and update it via onSortChange — you sort the data yourself.

<Table
  aria-label="Files"
  sortDescriptor={sortDescriptor}
  onSortChange={setSortDescriptor}
>
  <TableHeader>
    <TableColumn id="name" isRowHeader allowsSorting>
      Name
    </TableColumn>
  </TableHeader>
  <TableBody items={sortedItems}>{/* ... */}</TableBody>
</Table>

Async loading

Pair useAsyncList with TableBody's onLoadMore and isLoading for infinite loading; TableLoadMore renders the loading row.

const list = useAsyncList({ async load({ signal }) { /* ... */ } })

<TableBody
  onLoadMore={list.loadMore}
  isLoading={list.loadingState === 'loadingMore'}
>
  {/* ... */}
</TableBody>

Examples

Basic Table

Name
Type
Date Modified
GamesFile folder6/7/2020
Program FilesFile folder4/7/2021
bootmgrSystem file11/20/2010

Invoices Table

Invoice
Status
Method
Amount
INV001PaidCredit Card$250.00
INV002PendingPayPal$150.00
INV003UnpaidBank Transfer$350.00
INV004PaidCredit Card$450.00
INV005PaidPayPal$550.00
INV006PendingBank Transfer$200.00
INV007UnpaidCredit Card$300.00
Total$2,250.00

Project Budget

Project
Status
Team
Budget
Website RedesignPaidFrontend Team$12,500
Mobile AppUnpaidMobile Team$8,750
API IntegrationPendingBackend Team$5,200
Database MigrationPaidDevOps Team$3,800
User DashboardPaidUX Team$7,200
Security AuditFailedSecurity Team$2,100
Total Budget$39,550

Task Management

Welcome back!

Here's a list of your tasks for this month.

Task
Title
Status
Priority
Actions
TASK-1024
DocumentationOverriding the microchip won't do anything, we need to transmit the digital OCR transmitter!
In Progress
Low
TASK-1138
FeatureGenerating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!
In Progress
Medium
TASK-1280
BugUse the digital TLS panel, then you can transmit the haptic system!
Done
High
TASK-1571
FeatureI'll input the neural DRAM circuit, that should protocol the SMTP interface!
In Progress
Medium
TASK-1780
DocumentationThe CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!
Todo
High
TASK-2197
DocumentationParsing the feed won't do anything, we need to copy the bluetooth DRAM bus!
Todo
Low
TASK-2858
BugWe need to override the online UDP bus!
Backlog
Medium
TASK-3216
DocumentationTransmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!
Backlog
Medium
TASK-5160
DocumentationCalculating the bus won't do anything, we need to navigate the back-end JSON protocol!
In Progress
High
TASK-5326
BugWe need to hack the redundant UTF8 transmitter!
Todo
Low
0 of 30 row(s) selected.
Rows per page
Page 1 of 3

Issue Groups

Issue
Description
To-do4
ISS-1Signup flow broken on mobile devicesUsers on iOS and Android devices cannot complete account creation from the hosted form.
ISS-2Stripe payment failures in BrazilCustomers in Brazil are seeing card declines during subscription renewals.
ISS-3Dashboard not loading for enterprise customersEnterprise tier customers are seeing a blank dashboard after switching organizations.
ISS-12Email notifications not being deliveredTransactional emails including invites and receipts are delayed for some workspaces.
In progress3
ISS-4Search functionality returning incorrect resultsThe global search feature is returning stale records after index refreshes.
ISS-5Profile images not displaying correctlyUser profile images are appearing as initials after a recent media migration.
ISS-13Application crashes on specific user actionsUsers are experiencing application crashes when opening archived projects.
Attention required3
ISS-6Data export timing out for large workspacesCSV exports for larger teams fail after sixty seconds and need background processing.
ISS-7Webhook retries saturating delivery queueFailed webhook deliveries are retrying too aggressively during provider incidents.
ISS-8SSO login loop after session refreshSome SAML users are redirected back to sign in after a successful identity handshake.
Name
URL
Date added
Adobehttps://adobe.com/January 28, 2023
Googlehttps://google.com/April 5, 2023
New York Timeshttps://nytimes.com/July 12, 2023

API Reference

TableContainer

The scrollable container for a Table. Set `resizable` to opt into React Aria's resizable table layout.

PropType
boolean
ReactNode

Table

A table displays data in rows and columns and enables a user to navigate its contents via directional navigation keys, and optionally supports row selection and sorting.

PropType
ReactNode
Iterable<Key>
DragAndDropHooks
Iterable<Key>
SortDescriptor
Key
SelectionMode
SelectionBehavior
Iterable<Key> | "all"
Iterable<Key> | "all"
function
Iterable<Key>
DisabledBehavior
boolean
boolean
"clearSelection" | "none"

TableHeader

A header within a Table, containing the table columns.

PropType
union
Iterable<T>
readonly any[]

TableColumn

A column within a Table.

PropType
boolean
boolean
ReactNode | function
ColumnSize | null
Key
boolean
ColumnStaticSize | null
ColumnStaticSize | null
string
ColumnSize | null

TableBody

The body of a Table, containing the table rows.

PropType
boolean
ReactNode | function
Iterable<T>
function
function
readonly any[]

TableFooter

A footer within a Table, containing summary rows.

PropType
ReactNode | function
Iterable<T>
readonly any[]

TableRow

A row within a Table.

PropType
string
boolean
Key
boolean
T
union
Iterable<T>
readonly any[]
DisabledBehavior

TableCell

A cell within a table row.

PropType
ReactNode | function
number
Key
string

TableLoadMore

A loading row that can be used for infinite loading.

PropType
ReactNode
boolean
number
function

TableDropIndicator

A drop indicator rendered between table rows during drag and drop.

PropType
ReactNode | function

TableVirtualizer

A Virtualizer configured with the table layout used by Table.

PropType
number
number
ReactNode
T

Last updated on 7/7/2026