Name | Type | Date Modified |
|---|---|---|
| Games | File folder | 6/7/2020 |
| Program Files | File folder | 4/7/2021 |
| bootmgr | System file | 11/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/tableUsage
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 |
|---|---|---|
| Games | File folder | 6/7/2020 |
| Program Files | File folder | 4/7/2021 |
| bootmgr | System file | 11/20/2010 |
Invoices Table
Invoice | Status | Method | Amount |
|---|---|---|---|
| INV001 | Paid | Credit Card | $250.00 |
| INV002 | Pending | PayPal | $150.00 |
| INV003 | Unpaid | Bank Transfer | $350.00 |
| INV004 | Paid | Credit Card | $450.00 |
| INV005 | Paid | PayPal | $550.00 |
| INV006 | Pending | Bank Transfer | $200.00 |
| INV007 | Unpaid | Credit Card | $300.00 |
| Total | $2,250.00 | ||
Project Budget
Project | Status | Team | Budget |
|---|---|---|---|
| Website Redesign | Paid | Frontend Team | $12,500 |
| Mobile App | Unpaid | Mobile Team | $8,750 |
| API Integration | Pending | Backend Team | $5,200 |
| Database Migration | Paid | DevOps Team | $3,800 |
| User Dashboard | Paid | UX Team | $7,200 |
| Security Audit | Failed | Security 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 |
Issue Groups
Issue | Description | |
|---|---|---|
| To-do4 | ||
| ISS-1Signup flow broken on mobile devices | Users on iOS and Android devices cannot complete account creation from the hosted form. | |
| ISS-2Stripe payment failures in Brazil | Customers in Brazil are seeing card declines during subscription renewals. | |
| ISS-3Dashboard not loading for enterprise customers | Enterprise tier customers are seeing a blank dashboard after switching organizations. | |
| ISS-12Email notifications not being delivered | Transactional emails including invites and receipts are delayed for some workspaces. | |
| In progress3 | ||
| ISS-4Search functionality returning incorrect results | The global search feature is returning stale records after index refreshes. | |
| ISS-5Profile images not displaying correctly | User profile images are appearing as initials after a recent media migration. | |
| ISS-13Application crashes on specific user actions | Users are experiencing application crashes when opening archived projects. | |
| Attention required3 | ||
| ISS-6Data export timing out for large workspaces | CSV exports for larger teams fail after sixty seconds and need background processing. | |
| ISS-7Webhook retries saturating delivery queue | Failed webhook deliveries are retrying too aggressively during provider incidents. | |
| ISS-8SSO login loop after session refresh | Some SAML users are redirected back to sign in after a successful identity handshake. |
Links & Bookmarks
Name | URL | Date added |
|---|---|---|
| Adobe | https://adobe.com/ | January 28, 2023 |
| https://google.com/ | April 5, 2023 | |
| New York Times | https://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.
| Prop | Type | Default | |
|---|---|---|---|
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.
| Prop | Type | Default | |
|---|---|---|---|
ReactNode | — | ||
Iterable<Key> | — | ||
DragAndDropHooks | — | ||
Iterable<Key> | — | ||
SortDescriptor | — | ||
Key | — | ||
SelectionMode | — | ||
SelectionBehavior | 'toggle' | ||
Iterable<Key> | "all" | — | ||
Iterable<Key> | "all" | — | ||
function | — | ||
Iterable<Key> | — | ||
DisabledBehavior | 'all' | ||
boolean | — | ||
boolean | — | ||
"clearSelection" | "none" | 'clearSelection' | ||
TableHeader
A header within a Table, containing the table columns.
| Prop | Type | Default | |
|---|---|---|---|
union | — | ||
Iterable<T> | — | ||
readonly any[] | — | ||
TableColumn
A column within a Table.
| Prop | Type | Default | |
|---|---|---|---|
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.
| Prop | Type | Default | |
|---|---|---|---|
boolean | — | ||
ReactNode | function | — | ||
Iterable<T> | — | ||
function | — | ||
function | — | ||
readonly any[] | — | ||
TableFooter
A footer within a Table, containing summary rows.
| Prop | Type | Default | |
|---|---|---|---|
ReactNode | function | — | ||
Iterable<T> | — | ||
readonly any[] | — | ||
TableRow
A row within a Table.
| Prop | Type | Default | |
|---|---|---|---|
string | — | ||
boolean | — | ||
Key | — | ||
boolean | — | ||
T | — | ||
union | — | ||
Iterable<T> | — | ||
readonly any[] | — | ||
DisabledBehavior | — | ||
TableCell
A cell within a table row.
| Prop | Type | Default | |
|---|---|---|---|
ReactNode | function | — | ||
number | — | ||
Key | — | ||
string | — | ||
TableLoadMore
A loading row that can be used for infinite loading.
| Prop | Type | Default | |
|---|---|---|---|
ReactNode | — | ||
boolean | — | ||
number | 1 | ||
function | — | ||
TableDropIndicator
A drop indicator rendered between table rows during drag and drop.
| Prop | Type | Default | |
|---|---|---|---|
ReactNode | function | — | ||
TableVirtualizer
A Virtualizer configured with the table layout used by Table.
| Prop | Type | Default | |
|---|---|---|---|
number | — | ||
number | — | ||
ReactNode | — | ||
T | — |
Last updated on 7/7/2026