Node Sizing and Expandable Cards
Fixed node sizes are the limit on anything card-shaped: a card sized for the longest job title wastes space on every other node. Two layers lift that limit, and both feed the single source of truth that the layout, camera, edges and motion already read.
Per-node sizing
Any node's options may carry its own nodeWidth / nodeHeight, overriding the global value for that node only:
const data = {
id: 'ceo',
name: 'Alex Rivera',
options: { nodeWidth: 220, nodeHeight: 90 },
children: [
{ id: 'vp1', name: 'Mia Chen', children: [] },
{ id: 'vp2', name: 'Ben Haas', options: { nodeHeight: 70 }, children: [] },
],
}
const tree = new ApexTree(el, { nodeWidth: 160, nodeHeight: 60 })
tree.render(data)
Edges anchor to each card's own half-size, so mixed-size siblings separate correctly instead of overlapping or leaving a gap.
Measured heights
autoNodeHeight measures each card's rendered content in a reused offscreen box and sets the node's height from it, keeping the width fixed:
const tree = new ApexTree(el, {
nodeWidth: 200,
autoNodeHeight: {
enabled: true,
minHeight: 56,
maxHeight: 220,
extraHeight: 8,
},
})
| Option | Type | Default | Description |
|---|---|---|---|
autoNodeHeight.enabled | boolean | false | Measure content and size each node to fit |
autoNodeHeight.minHeight | number | 0 | Lower clamp in pixels; 0 means no floor |
autoNodeHeight.maxHeight | number | 0 | Upper clamp in pixels; 0 means no cap |
autoNodeHeight.extraHeight | number | 0 | Extra vertical padding added to the measured height |
Content taller than maxHeight is capped and clipped by the node's overflow: hidden, so set it with the tallest card you are willing to render.
Precedence is explicit, then measured, then global: a node's own options.nodeHeight wins over a measured height, which wins over the global nodeHeight.
Measurement needs a DOM, so it returns nothing under SSR or in jsdom, which keeps server rendering and tests deterministic. It applies to cartesian directions only: grouped-leaf and radial layouts stay uniform.
Expandable cards
A card can expand in place to reveal a detail section. This is distinct from expanding a node's children, and the two gestures coexist on the same node.
const tree = new ApexTree(el, {
autoNodeHeight: { enabled: true },
cardExpansion: { clickToExpand: true },
})
const graph = tree.render(data)
graph.toggleCard('alice')
graph.getExpandedCards() // ['alice']
| Method | Description |
|---|---|
expandCard(nodeId) | Reveal a node's detail section |
collapseCard(nodeId) | Hide it again |
toggleCard(nodeId) | Toggle it |
setExpandedCards(nodeIds) | Replace the whole set and reflow once |
getExpandedCards() | Ids of every currently expanded card |
cardExpansion.clickToExpand (default false) toggles the card on a card-body click. The built-in chevron and the methods above work whether or not it is set. Clicks on the expand/collapse-children button still toggle children, not the card.
Expansion only grows a card when its height can change, so it needs autoNodeHeight enabled (or a global nodeHeight already large enough to hold the detail section). Expansion reflows through the same reconciler as everything else, so siblings spring apart rather than jumping.
Card content fields
With contentKey pointing at an object, the built-in template understands a canonical shape. Some fields show in the summary, others only once the card is expanded:
| Field | Shape | Shown |
|---|---|---|
name, title, subtitle | string | Always |
imageURL, accentColor | string | Always |
badge | { text, color? } | Always |
meta | { label, icon? }[] | Always |
tags | string[] | Always, in the summary |
stats | { label, value }[] (both strings) | Only when expanded |
progress | { value, label?, color? }, value 0..100 | Only when expanded |
actions | { label, href? }[] | Only when expanded |
details | string | Only when expanded |
const data = {
id: 'alice',
data: {
name: 'Alice Chen',
title: 'VP Engineering',
accentColor: '#5C6BC0',
tags: ['Platform', 'Hiring'],
stats: [{ label: 'Reports', value: '24' }, { label: 'Open roles', value: '3' }],
progress: { label: 'Q3 roadmap', value: 68 },
details: 'Owns the platform and infrastructure groups.',
actions: [{ label: 'Profile', href: '/people/alice' }],
},
children: [],
}
const tree = new ApexTree(el, {
contentKey: 'data',
autoNodeHeight: { enabled: true },
cardExpansion: { clickToExpand: true },
})
Custom templates
A custom nodeTemplate receives expanded on its context argument, so it can render its own detail section. Mark your own toggle with data-apextree-card-toggle and ApexTree wires the click for you:
nodeTemplate: (content, context) => {
const expanded = context?.expanded ?? false
return `
<div class="card">
<strong>${content.name}</strong>
<button data-apextree-card-toggle aria-expanded="${expanded}">
${expanded ? 'Less' : 'More'}
</button>
${expanded ? `<p class="detail">${content.details ?? ''}</p>` : ''}
</div>
`
}
The chevron's accessible labels come from locale.messages.expandCardLabel and collapseCardLabel. See Localization and RTL.