Skip to content

Expanding and tree data

Rows can have rows. Tell the grid where a row’s children are and it renders them indented under their parent, behind a chevron.

Omar PrabhakaranDesignerSupportActive$0.00
Vihaan RossiDirectorDesignActive$1,073.00
Noah TanakaLeadSupportActive$0.00
Kwame KhanAnalystFinanceActive$48.00
Mia KimLeadDataActive$0.00
Mia MüllerDirectorResearchPaused$406.00
Yuki RossiLeadDataInvited$290.00
Idris PatelInternSalesActive$12.00
Rohan MenonDirectorResearchActive$36.00
Idris PrabhakaranEngineerSalesActive$36.00
Sofia RossiDirectorSupportChurned$0.00
0 of 40 row(s) selected.
Page 1 of 4

Two things: getSubRows, so the grid knows where the children live, and col.expander(), the column that holds the chevron.

const columns = col.columns([
col.expander(),
col.select(),
col.text("name", { header: "Name" }),
col.text("team", { header: "Team" }),
])
<DataGrid
data={data}
columns={columns}
getRowId={(row) => row.id}
getSubRows={(row) => row.subRows}
initialState={{ expanded: { p00000: true } }}
/>
  • getSubRows returns the array of children for a row, or undefined for a leaf. Any depth works.
  • col.expander() renders a chevron for rows that have children and a blank space of the same width for rows that don’t, so the columns after it stay aligned. Each level indents the chevron a further 16 px. Put it first.
  • The expander’s header is an Expand all / Collapse all button. It renders only when at least one row can expand.
  • initialState.expanded is a map of row id to true (the ids getRowId returns), or true to start with everything open.

From code: row.toggleExpanded(), row.getIsExpanded(), table.toggleAllRowsExpanded(), table.setExpanded(...), table.resetExpanded().

  • Ids. With getRowId, the keys in expanded are your ids. Without it, TanStack numbers rows by their path ("0", "0.2"), which changes as data changes; give the grid stable ids.
  • Paging counts top-level rows. Expanded children are not paginated separately (paginateExpandedRows: false): they render on their parent’s page, so a page can show more rows than its page size.
  • Selection follows the tree. Selecting a parent selects its children; a parent with some children selected shows an indeterminate checkbox. Space on a focused cell selects the row with its children. Turn it off with selection={{ subRows: false }}.
  • Sorting runs at every level: children are sorted within their parent.
  • Filtering tests the parents first. A parent that fails is removed with its children; a parent that passes keeps only the children that pass. To keep a parent whenever any descendant matches, pass table={{ filterFromLeafRows: true }}.
  • Expanded state survives data changes (autoResetExpanded: false): refetching the rows does not fold the tree.
  • Export writes the rows as they are expanded. Collapsed children are not exported.
  • Grouping uses the same machinery: a group row is an expandable row whose children are the group’s members. See Grouping and aggregation.
  • Server-side trees. With server={{ expanding: true }} the grid stops expanding locally and you deliver the expanded rows yourself. See Server-side data.
Key Does
Enter on a focused cell Toggles the row’s children, when the cell is not editable.
Space Selects the row and its children.
Tab to the chevron, Enter The chevron is a button: it toggles the row.

Every row carries aria-level (its depth, from 1) and, when it has children, aria-expanded. The chevron is labelled Expand or Collapse; the header button Expand all or Collapse all. All four strings are in labels.

Grid options:

Option Type Default What it does
getSubRows (row) => TData[] Where a row’s children are; return undefined for a leaf. Without it no row can expand.
initialState.expanded ExpandedState {} Which rows start open, as { [rowId]: true }; true opens them all.
selection.subRows boolean true Whether selecting a parent selects its children.
table.getRowCanExpand (row) => boolean Override the default (has sub-rows). Useful for lazy children: expand first, load later.
table.filterFromLeafRows boolean false Keep a parent whenever any descendant passes the filter.
table.paginateExpandedRows boolean false true lets children spill across pages.

Column helper:

Helper What it does
col.expander(options?) The chevron column: id "expander", 40 px fixed, no sorting, filtering, hiding or grouping. options merge over the defaults.

Cells exported for your own columns: DataGridExpanderCell, DataGridExpanderHeader.