Table
Preview: Selectable
Data table on React Aria Table: sorting, row selection, keyboard grid navigation. Wrap in a bordered container for the card look.
Import
import { Table, TableHeader, Column, TableBody, Row, TableCell, … } from '@marbleui/ui'Props
<Table>
| Prop | Type | Default | Description |
|---|---|---|---|
| density | Density | 'regular' | |
| striped | boolean | false |
Also accepts React Aria props (98).
Accessibility
Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria Cell · React Aria Collection · React Aria Column · React Aria Row · React Aria Table · React Aria TableBody · React Aria TableHeader. Follow the primitive's docs for the full keyboard map.
Usage notes
Static or lightly interactive table on React Aria Table: sorting, selection and grid keyboard navigation (arrows move between cells).
<Table aria-label="Invoices" sortDescriptor={sort} onSortChange={setSort}>
<TableHeader><Column id="number" isRowHeader allowsSorting>Number</Column><Column id="total">Total</Column></TableHeader>
<TableBody items={rows}>{(r) => <Row id={r.id}><Cell>{r.number}</Cell><Cell>{r.total}</Cell></Row>}</TableBody>
</Table>
- Sorting is controlled: you sort
rowsyourself fromsortDescriptor. - Every column header needs text; for an actions column use visually hidden text.
- Need search, pagination or column toggles? Use
DataGrid.
Example source · show· hide
import { useMemo, useState } from 'react'
import type { SortDescriptor } from 'react-aria-components'
import { Badge } from '../badge'
import { Cell, Column, Row, Table, TableBody, TableHeader } from './table'
type Invoice = { id: string; customer: string; status: 'Paid' | 'Pending' | 'Overdue'; amount: number; date: string }
const invoices: Invoice[] = [
{ id: 'INV-1042', customer: 'Northwind', status: 'Paid', amount: 1280, date: '2026-10-02' },
{ id: 'INV-1043', customer: 'Globex', status: 'Pending', amount: 640, date: '2026-10-05' },
{ id: 'INV-1044', customer: 'Initech', status: 'Overdue', amount: 2300, date: '2026-09-21' },
{ id: 'INV-1045', customer: 'Umbrella', status: 'Paid', amount: 410, date: '2026-10-11' },
{ id: 'INV-1046', customer: 'Hooli', status: 'Pending', amount: 980, date: '2026-10-14' },
]
const tone = { Paid: 'success', Pending: 'warning', Overdue: 'danger' } as const
const money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
const Frame = ({ children }: { children: React.ReactNode }) => (
<div className="w-[36rem] max-w-full overflow-auto rounded-lg border border-border bg-surface">{children}</div>
)
function Sortable() {
const [sort, setSort] = useState<SortDescriptor>({ column: 'date', direction: 'descending' })
const rows = useMemo(
() =>
[...invoices].sort((a, b) => {
const k = sort.column as keyof Invoice
const d = a[k] < b[k] ? -1 : a[k] > b[k] ? 1 : 0
return sort.direction === 'descending' ? -d : d
}),
[sort],
)
return (
<Frame>
<Table aria-label="Invoices" sortDescriptor={sort} onSortChange={setSort} selectionMode="multiple">
<TableHeader>
<Column id="id" isRowHeader allowsSorting>Invoice</Column>
<Column id="customer" allowsSorting>Customer</Column>
<Column id="status">Status</Column>
<Column id="date" allowsSorting>Date</Column>
<Column id="amount" allowsSorting className="text-right">Amount</Column>
</TableHeader>
<TableBody items={rows}>
{(r) => (
<Row id={r.id}>
<Cell className="font-medium">{r.id}</Cell>
<Cell>{r.customer}</Cell>
<Cell><Badge tone={tone[r.status]} dot>{r.status}</Badge></Cell>
<Cell className="text-fg-muted">{r.date}</Cell>
<Cell className="text-right">{money.format(r.amount)}</Cell>
</Row>
)}
</TableBody>
</Table>
</Frame>
)
}
export const styles = {
Selectable: Sortable,
Compact: () => (
<Frame>
<Table aria-label="Invoices" density="compact" striped>
<TableHeader>
<Column isRowHeader>Invoice</Column>
<Column>Customer</Column>
<Column className="text-right">Amount</Column>
</TableHeader>
<TableBody items={invoices}>
{(r) => (
<Row id={r.id}>
<Cell className="font-medium">{r.id}</Cell>
<Cell>{r.customer}</Cell>
<Cell className="text-right">{money.format(r.amount)}</Cell>
</Row>
)}
</TableBody>
</Table>
</Frame>
),
Empty: () => (
<Frame>
<Table aria-label="Invoices">
<TableHeader>
<Column isRowHeader>Invoice</Column>
<Column>Customer</Column>
<Column>Amount</Column>
</TableHeader>
<TableBody renderEmptyState={() => <div className="py-10 text-center text-sm text-fg-muted">No invoices yet.</div>}>
{[]}
</TableBody>
</Table>
</Frame>
),
}Source: packages/ui/src/table/table.tsx
Catalog
- Category
- collections
- Priority
- Foundation
- Primitive
- react-aria
- React Aria
- Table