Skip to content

Datagrid tree

Hierarchy as lazy hypermedia, table-shaped: every row carries aria-level, an expandable row carries aria-expanded + a lead-cell toggle, and the first expand fires hc:datagridtreeload once — htmx GETs the children and inserts them right after the row (afterend). Collapse and re-expand after that are pure client-side visibility; the hierarchy itself stays server-owned. This is the lazy-tree recipe for the datagrid. Covered by the versioning policy.

Expand docs — the children arrive from the server one level deeper (the guide sub-directory brings its own toggle; expand it for level 3). Collapse and re-open: no second request. src shows the empty-state row branch.

NameSize
README.md4 KB
<tbody class="hc-datagrid__body">
<tr class="hc-datagrid__row" id="node-docs" aria-level="1"
aria-expanded="false" data-lazy
data-hx-get="/items/docs/children"
data-hx-trigger="hc:datagridtreeload"
data-hx-swap="afterend">
<td class="hc-datagrid__cell">
<button class="hc-datagrid__toggle" type="button"
data-hc-datagrid-tree aria-hidden="true" tabindex="-1"></button>
docs
</td>
<td class="hc-datagrid__cell" data-numeric></td>
</tr>
<tr class="hc-datagrid__row" aria-level="1">
<td class="hc-datagrid__cell">README.md</td>
<td class="hc-datagrid__cell" data-numeric>4 KB</td>
</tr>
</tbody>

installDatagrid() upgrades the table to role="treegrid" when tree toggles exist — the role under which row-level aria-level / aria-expanded are valid. The toggle button stays aria-hidden (mouse affordance); Enter on the lead cell covers keyboard, and the row announces the state. Levels 2–4 indent the lead cell (--hc-datagrid-indent).

GET /items/:id/children:

CaseResponse (200)
children existthe child <tr> batch, each aria-level="n+1"; a child with children brings its own toggle + lazy wiring; leaves carry aria-level only
emptyone empty-state row (single colspan cell, polite text) — never an empty body: the arriving row is also what clears the parent’s aria-busy
unknown id404 — surface via the standard error toast

Grandchildren load from their own parents — the server renders direct children only. A re-sort / re-filter / page change re-renders the whole grid fragment; loaded subtrees are gone by design.

Render the tree pre-expanded to a sensible depth (rows are plain <tr>s), or link the lead cell to a drill-down page — the hierarchy is navigable as pages without any script.

  • role="treegrid" + row-level aria-level / aria-expanded — the hierarchy is announced, not just indented.
  • The aria-hidden toggle keeps a single announced control per row; keyboard toggling is Enter on the lead cell.
  • The empty-state row renders “nothing here” instead of implying it by silence.
  • lazy-tree — the same economics for hc-tree lists.
  • datagrid-infinite — unbounded flat lists for the same grid.
  • Datagrid — the component’s tree-rows section documents the behavior side.