Grouping & pivot
Tree data
Show hierarchical rows as an expandable tree, from children you already have or loaded from a server on expand.
Tree data shows rows that contain rows: folders and files, an org chart, accounts and sub-accounts. Tell the grid how to find a row’s children, and rows become the roots of an indented tree with a chevron on every row that has children.
Expand the folders with a click, or with → and ← on the focused row.
Children you already have
getChildren returns a row’s children, or undefined (or an empty array) for a leaf:
<script lang="ts">
import { Grid, type GridRow } from 'bo-grid';
let rows = $state<GridRow[]>([
{ id: 1, name: 'src', children: [{ id: 2, name: 'app.ts' }] },
{ id: 3, name: 'README.md' },
]);
</script>
<Grid {rows} {columns} height={520} getChildren={(r) => r.children as GridRow[] | undefined} /> The first column is indented by depth and carries the chevron. Every row in the tree needs an id unique across the whole tree, not just among its siblings.
In tree mode the grid shows the tree as given: filtering, sorting, grouping and pagination do not apply to it.
Children loaded on expand
For a hierarchy too large to send up front, use loadChildren instead of getChildren. It returns a promise of a row’s children and runs the first time the row is expanded; the grid shows a loading row meanwhile and keeps the children once loaded. Pair it with hasChildren, a cheap test that decides which rows get a chevron before anything loads:
<Grid
{rows}
{columns}
height={520}
hasChildren={(r) => r.kind === 'folder'}
loadChildren={(r) => fetch(`/api/children/${r.id}`).then((res) => res.json())}
/> A server-backed tree, with a loading row on each first expand:
Keyboard and screen readers
With tree data the grid takes the treegrid role: rows expose aria-level and aria-expanded, → expands a collapsed row and ← collapses an expanded one. Other keys work as in a flat grid; see Keyboard & hotkeys.
Reference
getChildren (row: GridRow) => GridRow[] | undefined Tree data: return a row's children (undefined/empty = leaf). When set, rows are the roots; the grid renders an indented, expandable tree. In-memory mode; filter/sort/group/paginate are not applied to the tree.
Example
<Grid rows={roots} {columns} height={480} getChildren={(row) => row.children as GridRow[] | undefined} />loadChildren (row: GridRow) => Promise<GridRow[]> Async tree data: load a row's children on expand (server-backed trees). Returns a promise; the grid shows a loading row, then caches the result. Pair with hasChildren (a cheap predicate) so chevrons show without loading. Use instead of getChildren. In-memory roots.
Example
<Grid rows={roots} {columns} height={480}
loadChildren={(row) => fetch(`/api/accounts/${row.id}/children`).then((r) => r.json())}
hasChildren={(row) => Number(row.childCount) > 0} />hasChildren (row: GridRow) => boolean Cheap predicate for whether a row has children (drives the expand chevron without loading). Required with loadChildren; optional with getChildren.
Example
<Grid rows={roots} {columns} height={480} {loadChildren} hasChildren={(row) => row.kind === 'folder'} />