React table expandable rows — detail panels
▶ Try it live: open a Mantine starter in StackBlitz — a real AdaptTable you can edit in the browser, no install. Other UI kits →
▶ See it working: nested tables in Mantine — open a row onto another table, not a blank panel. The same page exists for MUI, Chakra, antd, Radix, Base UI, shadcn and Tailwind.
Render a detail panel under any row by passing renderRowDetail — its
presence is the whole API.
Example
Section titled “Example”import { DataTable } from "@adapttable/mantine"; // or @adapttable/mui, chakra, antd, radix, shadcn, unstyled
interface Order { id: string; customer: string; total: number; items: string[];}
const data: Order[] = [ { id: "1", customer: "Aisha", total: 240, items: ["Desk", "Lamp"] }, { id: "2", customer: "Jonas", total: 90, items: ["Chair"] }, { id: "3", customer: "Mei", total: 410, items: ["Monitor", "Dock", "Cable"] },];
export function Orders() { return ( <DataTable data={data} columns={[{ key: "customer", sortable: true }, { key: "total" }]} rowKey={(r) => r.id} renderRowDetail={(row) => ( <ul> {row.items.map((item) => ( <li key={item}>{item}</li> ))} </ul> )} /> );}How it works
Section titled “How it works”- Passing
renderRowDetail(row)enables a leading expand chevron on desktop rows and a detail section on mobile cards — no extra flag.defaultExpandedRowIdsopens those rows on the first render; after that the reader’s toggles own the set. - Multiple rows may be open at once. Expansion is keyed by row id, so an open panel survives sorting and paging: a row that leaves the page simply re-opens when it returns.
- The chevron carries
aria-expandedplus theexpandRow/collapseRowlabels (defaults “Expand row” / “Collapse row”; the i18n presets translate them). - Ant Design maps the contract onto its native
expandableAPI (expandedRowKeys/expandedRowRender), with a custom expand icon so thelabels.expandRow/labels.collapseRowcontract still applies instead of antd’s ConfigProvider locale. - The state lives in a headless hook,
useRowExpansion()— use it for custom markup:{ expandedIds, isExpanded(id), toggle(id) }.
Options
Section titled “Options”| Prop | Type | Default | Description |
|---|---|---|---|
renderRowDetail |
(row: TRow) => ReactNode |
— | Detail-panel renderer; its presence enables expansion. |
defaultExpandedRowIds |
readonly string[] |
— | Row ids whose panel starts open. Later toggles own the set. |
labels |
TableLabels |
English | Override expandRow / collapseRow for the chevron’s accessible label. |
rowKey |
(row: TRow) => string |
— | Already required; expansion state is keyed by this id. |
- Works with
virtualize. A table cannot nest a detail panel inside the row it belongs to, so the two are separate elements — the window measures them together, and an open panel reports its real height instead of its row’s. A panel that grows later (an image loading, a nested table opening) corrects the height as it happens. - The detail cell spans the full row width, including selection, actions, and the expansion column itself.
- Kits with native expand affordances keep their own look; only the labels
are unified through
labels.
See it live in the demo.