webdevpuneet.com

Tree Table — Expandable Hierarchy Rows HTML CSS JS

Tree Table · Tables · Plain HTML, CSS & JS · Live preview

CategoryTables

Copy & paste

Source Code

<div class="trt-card">
  <div class="trt-head">
    <h3>Budget by department</h3>
    <button type="button" class="trt-toggle-all" id="trtToggleAll">Collapse all</button>
  </div>
  <table class="trt-table">
    <thead>
      <tr><th>Category</th><th class="trt-num">Budget</th><th class="trt-num">Spent</th><th class="trt-num">Remaining</th></tr>
    </thead>
    <tbody id="trtBody"></tbody>
  </table>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Unlimited nesting
A recursive flatten renders a tree of any depth as the flat rows a <table> requires, tagged with depth and parent.
Ancestor-aware visibility
A row shows only when every ancestor is expanded, so collapsing any level correctly hides all its descendants.
Disclosure triangles
Rotating triangles indicate expand/collapse state, with invisible spacers keeping leaf rows aligned.
Depth indentation and styling
Each level indents and lightens, reinforcing the hierarchy beyond indentation alone.
Expand-all / collapse-all
One header button opens or closes the entire tree, essential for deep hierarchies.
Per-row roll-up bar
A mini utilization bar fills proportionally and flags overspend, giving context down the tree.
Simple expansion state
An id→boolean map is the single source of truth that the render reads — no scattered DOM state.
Semantic table
A real <table> with thead/tbody and button toggles, the accessible base for hierarchical data.

About this UI Snippet

Tree Table — Collapsible Nested Rows for Hierarchical Data

Screenshot of the Tree Table snippet rendered live

Some data is naturally a hierarchy — a budget by department and sub-team, a file system, a category tree, an org chart of accounts — and a flat table can't show those parent/child relationships. A tree table solves it: rows nest under expandable parents, indented by depth, so you can drill from a top-level total down to its details and collapse what you don't need. This snippet builds that pattern in plain HTML, CSS, and vanilla JavaScript over a real <table>, with any depth of nesting.

Nested data, flattened for rendering

The source is a nested tree of objects, each with optional children. flatten() walks that tree depth-first into a flat list of rows, each tagged with its depth and parent id — because a <table> body is a flat list of <tr>s, not a nested structure. This flatten-with-metadata step is the core technique: it lets you keep your data as a natural hierarchy while rendering it as the linear rows a table requires, and it works for unlimited depth (the demo goes three levels deep).

Visibility derived from ancestor expansion

A child row is visible only when *every* ancestor above it is expanded — collapsing a top-level row should hide its grandchildren too, not just its direct children. isVisible() walks up the parent chain and returns false if any ancestor is collapsed, so the show/hide logic is always correct no matter how deep the nesting or which level you collapse. Expansion state lives in a simple expanded map of id → boolean, the single source of truth that the render reads.

Indentation and disclosure triangles

Each row indents by its depth (via a left-padding rule per depth level) and shows a rotating disclosure triangle that points right when collapsed and down when expanded — the universal tree-control affordance. Leaf rows (no children) get an invisible spacer where the triangle would be, so their text still aligns with siblings that do have triangles. Top-level rows are bold, deeper rows progressively lighter, reinforcing the hierarchy visually beyond just indentation.

Roll-up context per row

Because tree tables usually show aggregates (a parent's budget is the sum of its children's), each row here includes a little spend bar that fills proportionally and turns red past 90% — so you can scan utilization down the hierarchy. In a real app the parent values would be computed from the children (covered in the FAQs); the structure supports either pre-computed or rolled-up totals.

Expand-all / collapse-all

A header button toggles the entire tree open or closed at once — essential for a deep hierarchy where expanding level by level is tedious. It rebuilds the expanded map (all parent ids true, or empty) and re-renders, flipping its own label between "Expand all" and "Collapse all." The tree starts with the top level expanded so the structure is immediately visible without hiding everything.

A real table, kept accessible

It's a genuine semantic <table> with <thead>/<tbody>, so it reads as tabular data to assistive tech and works with table styling. The disclosure triangles are real <button>s. For full accessibility you'd add aria-expanded to each toggle and role="treegrid" semantics (covered in the FAQs), but the foundation — semantic table, flattened render, ancestor-aware visibility — is the right base for any hierarchical table.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA budget tree table renders with departments expanded, showing nested sub-teams and line items.
  2. 2
    Collapse a branchClick a parent's triangle to collapse it — all its descendants hide, and the triangle rotates to point right.
  3. 3
    Drill downExpand a department, then a sub-team, to reach the deepest line items; indentation shows the depth.
  4. 4
    Toggle everythingUse "Collapse all" / "Expand all" in the header to open or close the whole tree at once.
  5. 5
    Read the spend barsEach row's mini bar shows budget utilization, turning red past 90%, so you can scan the hierarchy.
  6. 6
    Use your own dataReplace the DATA tree with your nested objects (name, values, optional children) — any depth renders.

Real-world uses

Common Use Cases

Budgets and financial breakdowns
Drill from totals to line items by department and sub-team — pair with a data table column toggle.
File and folder explorers
Show a directory tree with sizes and dates as expandable rows.
Category and taxonomy management
Manage nested product categories or tags in a hierarchical table.
Org charts and reporting lines
Display teams and reports as a collapsible tree of rows.
BOM and inventory hierarchies
Show bills of materials or nested inventory with roll-up quantities.
Learning tree rendering
A reference for flattening nested data and ancestor-aware visibility — compare with an expandable table for single-level row detail and a tree menu for navigation.

Got questions?

Frequently Asked Questions

Add a recursive aggregate function that, for any node with children, sums its children's values (which themselves may be sums) — compute budget/spent for parents as the total of their descendants rather than storing them. Run it once on the DATA tree before rendering, so every parent row shows the true roll-up of everything beneath it, and the numbers stay consistent as you edit leaves.

An HTML table body is a flat list of rows; nesting <table>s inside cells breaks column alignment and accessibility. Flattening the tree into rows tagged with depth and parent id keeps one aligned table while preserving the hierarchy in the data — indentation conveys depth visually, and the parent/depth metadata drives expand/collapse and visibility. It's the standard approach for tree tables.

Use role="treegrid" on the table, add aria-expanded (true/false) to each parent's toggle button reflecting its state, aria-level matching the depth on each row, and aria-setsize/aria-posinset for position. Ensure the toggles are keyboard-operable (they're buttons, so Enter/Space work) and consider arrow-key navigation between rows. The semantic <table> base makes layering these straightforward.

Mark nodes that have children but haven't loaded them (e.g. hasChildren: true, children: null), render their toggle, and on first expand fetch the children from your API, insert them into the node, and re-render. This avoids loading a massive tree upfront — the flatten/visibility logic works the same once children are populated.

In React, hold the expanded map (or a Set of ids) in useState and derive the flattened, visibility-filtered rows with useMemo, rendering with .map(); in Vue, use a reactive expanded object with a computed rows list; in Angular, use a component field and a getter. The flatten and isVisible functions are plain JavaScript that port unchanged — only the per-toggle re-render moves into the framework.