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.
Live demo
Section titled “Live demo”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.
| Name | Size |
|---|---|
| docs | — |
| src | — |
| README.md | 4 KB |
The markup
Section titled “The markup”<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).
Server response contract
Section titled “Server response contract”GET /items/:id/children:
| Case | Response (200) |
|---|---|
| children exist | the child <tr> batch, each aria-level="n+1"; a child with children brings its own toggle + lazy wiring; leaves carry aria-level only |
| empty | one 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 id | 404 — 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.
Progressive enhancement
Section titled “Progressive enhancement”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.
Accessibility
Section titled “Accessibility”role="treegrid"+ row-levelaria-level/aria-expanded— the hierarchy is announced, not just indented.- The
aria-hiddentoggle 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.
Related
Section titled “Related”- lazy-tree — the same
economics for
hc-treelists. - datagrid-infinite — unbounded flat lists for the same grid.
- Datagrid — the component’s tree-rows section documents the behavior side.