# 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 <kbd>→</kbd> and <kbd>←</kbd> on the focused row.

Live example: https://bonguynvan.github.io/bo-grid/examples/tree/ (source: https://github.com/bonguynvan/bo-grid/blob/main/src/demo/examples/)

## Children you already have

`getChildren` returns a row's children, or `undefined` (or an empty array) for a leaf:

```svelte
<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:

```svelte
<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:

Live example: https://bonguynvan.github.io/bo-grid/examples/lazytree/ (source: https://github.com/bonguynvan/bo-grid/blob/main/src/demo/examples/)

## Keyboard and screen readers

With tree data the grid takes the treegrid role: rows expose `aria-level` and `aria-expanded`, <kbd>→</kbd> expands a collapsed row and <kbd>←</kbd> collapses an expanded one. Other keys work as in a flat grid; see [Keyboard & hotkeys](https://bonguynvan.github.io/bo-grid/docs/keyboard/).

## 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.

  ```svelte
  <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.

  ```svelte
  <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`.

  ```svelte
  <Grid rows={roots} {columns} height={480} {loadChildren} hasChildren={(row) => row.kind === 'folder'} />
  ```

---
Page: https://bonguynvan.github.io/bo-grid/docs/tree-data/ · All pages: https://bonguynvan.github.io/bo-grid/llms.txt
