Expand and Collapse
Nodes with children can be collapsed to hide their subtree and expanded to reveal it. ApexTree provides a dedicated button, an optional click-to-toggle mode, count badges, batch verbs, and programmatic control.
Expand/collapse button
enableExpandCollapse (on by default) shows a +/- button on every node that has children:
const tree = new ApexTree(document.getElementById('chart'), {
enableExpandCollapse: true, // default
})
tree.render(data)
Style the button with:
| Option | Type | Default | Description |
|---|---|---|---|
expandCollapseButtonBGColor | string | '#FFFFFF' | Button background |
expandCollapseButtonBorderColor | string | '#E4E7EC' | Button border |
expandCollapseButtonIconColor | string | '#475467' | Color of the +/- glyph |
expandCollapseButtonSize | number | 15 | Button diameter in pixels |
expandCollapseButtonHaloColor | string | '' | Opaque ring drawn outside the button |
expandCollapseButtonSize is purely visual. The button carries an invisible hit area of max(size + 4, 24), so shrinking it never drops the tap target below the WCAG 2.2 SC 2.5.8 minimum of 24px. The chrome grows 15% on hover about its own centre while the hit area stays put, so the target never moves out from under the pointer.
Set expandCollapseButtonIconColor whenever you change expandCollapseButtonBGColor, or the glyph can end up invisible against the button fill.
The button straddles the node's edge, and its border shares a default grey with borderColor, so on a busy canvas it can dissolve into the node border and the incoming edge. expandCollapseButtonHaloColor draws an opaque ring just outside it to punch a clean hole through both. It is opt-in because the library cannot know what is painted behind the nodes:
const tree = new ApexTree(el, {
expandCollapseButtonHaloColor: '#F9FAFB', // your page/container background
})
Click-to-toggle
expandCollapseOnNodeClick makes the entire node body toggle its expansion, in addition to the dedicated button. The cursor becomes a pointer on toggleable nodes:
const tree = new ApexTree(el, {
expandCollapseOnNodeClick: true,
})
tree.render(data)
The toggle fires before onNodeClick, so your click callback sees the post-toggle state.
Collapse-count badge
When a node is collapsed, its hidden-descendant count renders inside the expand/collapse button, which widens into a pill:
| Option | Type | Default | Description |
|---|---|---|---|
collapseBadgeEnabled | boolean | true | Show the badge on collapsed nodes |
collapseBadgeThreshold | number | 1 | Minimum hidden children before the badge appears |
collapseBadgeBGColor | string | '#5C6BC0' | Badge background |
collapseBadgeFontColor | string | '#FFFFFF' | Badge text color |
collapseBadgeFontSize | string | '12px' | Badge font size |
const tree = new ApexTree(el, {
collapseBadgeEnabled: true,
collapseBadgeThreshold: 3, // only show the badge when 3+ children are hidden
collapseBadgeBGColor: '#F59E0B',
})
collapseBadgeFontSize is capped at 70% of expandCollapseButtonSize so a themed value cannot overflow the pill. The --apex-tree-badge-* CSS variables style the same pill.
Always-on count badges
The collapse badge only appears on a collapsed node. countBadgeEnabled shows a persistent badge on every node instead, independent of collapse state:
| Option | Type | Default | Description |
|---|---|---|---|
countBadgeEnabled | boolean | false | Show a count badge on every node |
countBadgeSource | 'descendants' | 'children' | 'data' | 'descendants' | What the number represents |
countBadgeDataKey | string | 'count' | Field read when countBadgeSource is 'data' |
countBadgeThreshold | number | 1 | Minimum count before the badge appears |
countBadgeBGColor | string | '#EEF2FF' | Badge background |
countBadgeFontColor | string | '#3730A3' | Badge text color |
countBadgeFontSize | string | '12px' | Badge font size |
const tree = new ApexTree(el, {
countBadgeEnabled: true,
countBadgeSource: 'children', // direct reports, not the whole subtree
})
With countBadgeSource: 'data', the value comes from each node's own data at countBadgeDataKey and is coerced with Number(...). Non-numeric or missing values render no badge, which lets you show a domain metric such as open headcount or ticket count:
const tree = new ApexTree(el, {
countBadgeEnabled: true,
countBadgeSource: 'data',
countBadgeDataKey: 'openRoles',
})
Programmatic expand/collapse
Call expand(nodeId) and collapse(nodeId) on the graph returned by render():
const graph = tree.render(data)
graph.collapse('vp2') // hide vp2's subtree
graph.expand('vp2') // reveal it again
Use getNodeMap() to traverse and act on specific nodes.
Batch verbs
Rather than looping over ids, use the batch verbs. Each runs as a single reflow, so the whole set springs together in one wave instead of one render per node:
| Method | Description |
|---|---|
expandAll() | Expand every node. No-op if nothing is collapsed |
collapseAll() | Collapse every node, leaving only the root visible |
expandToDepth(depth) | Show the tree down to depth (root = 0) |
expandSubtree(nodeId) | Expand a node and every one of its descendants |
collapseSubtree(nodeId) | Collapse a node and every one of its descendants |
const graph = tree.render(data)
document.getElementById('expand-all').onclick = () => graph.expandAll()
document.getElementById('collapse-all').onclick = () => graph.collapseAll()
document.getElementById('two-levels').onclick = () => graph.expandToDepth(2)
collapseAll() leaves each level's own collapsed state intact, so a later expand reveals one level at a time. collapseSubtree(id) behaves the same way for one branch.
The batch verbs walk the tree as it currently exists, so they do not trigger fetches for nodes behind an unloaded hasChildren: true. See Lazy Children.
Auto-zoom on toggle
enableExpandCollapseZoom (on by default) re-fits the viewBox to the visible nodes after each expand/collapse, springing to the new framing. Set it to false to keep the viewport fixed:
const tree = new ApexTree(el, {
enableExpandCollapseZoom: false, // don't auto-fit after toggling
})
maxZoomNodeSpan (default 8) caps how far that fit can zoom in, so collapsing down to a couple of nodes does not balloon them to fill the canvas. Set it to 0 for a tight fit. See Zoom, Pan and Export.
Grouping leaf nodes
groupLeafNodes stacks leaf nodes vertically instead of spreading them horizontally — useful for wide trees with many terminal nodes:
const tree = new ApexTree(el, {
groupLeafNodes: true,
groupLeafNodesSpacing: 12, // gap between stacked leaves (default: 10)
})
tree.render(data)
Grouped leaves are connected with an orthogonal side-bracket connector regardless of the global edgeStyle.
Animation
Expand and collapse are spring-driven when enableAnimation is true (the default). Entering nodes grow out of their parent, exiting nodes retract into it, edges are derived from their endpoints every frame, and the camera glides. A toggle that arrives while another is still in flight redirects the motion rather than restarting it.
Set enableAnimation: false for instant toggles. See Motion and Animation for motion.spring, the wave stagger, and reduced-motion handling.
Complete example
const tree = new ApexTree(document.getElementById('chart'), {
enableExpandCollapse: true,
expandCollapseOnNodeClick: true,
collapseBadgeEnabled: true,
collapseBadgeThreshold: 1,
enableExpandCollapseZoom: true,
enableAnimation: true,
})
const graph = tree.render(data)
document.getElementById('collapse-all').onclick = () => graph.collapseAll()
document.getElementById('expand-all').onclick = () => graph.expandAll()
React example
import { useRef } from 'react'
import { ApexTreeChart } from 'react-apextree'
import type { ApexTreeRef } from 'react-apextree'
export default function CollapsibleTree() {
const ref = useRef<ApexTreeRef>(null)
return (
<div>
<button onClick={() => ref.current?.collapse('vp2')}>Collapse vp2</button>
<button onClick={() => ref.current?.expand('vp2')}>Expand vp2</button>
<ApexTreeChart
ref={ref}
data={data}
options={{ expandCollapseOnNodeClick: true, collapseBadgeThreshold: 2 }}
/>
</div>
)
}