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,
  },
})
OptionTypeDefaultDescription
autoNodeHeight.enabledbooleanfalseMeasure content and size each node to fit
autoNodeHeight.minHeightnumber0Lower clamp in pixels; 0 means no floor
autoNodeHeight.maxHeightnumber0Upper clamp in pixels; 0 means no cap
autoNodeHeight.extraHeightnumber0Extra 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']
MethodDescription
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:

FieldShapeShown
name, title, subtitlestringAlways
imageURL, accentColorstringAlways
badge{ text, color? }Always
meta{ label, icon? }[]Always
tagsstring[]Always, in the summary
stats{ label, value }[] (both strings)Only when expanded
progress{ value, label?, color? }, value 0..100Only when expanded
actions{ label, href? }[]Only when expanded
detailsstringOnly 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.