ApexTree 2.0.0 shipped on August 10, 2026 with a spring motion core that turns collapse, expand, layout changes and data updates into transitions instead of a tear-down and rebuild. No public option or method was removed, but four defaults change what an existing tree shows, so check the table below before upgrading.

Key takeaways

  • Spring motion core: nodes, edges and the camera spring to their new positions, and an interrupted gesture never snaps.
  • Live data: graph.updateData(data) diffs on node id, so surviving nodes move instead of redrawing.
  • Radial and dendrogram layouts: direction: 'radial' and layoutType: 'cluster'.
  • Large trees: measured node heights, expandable cards, focus mode, semantic zoom, lazy children and a Cmd/Ctrl-K command palette.
  • Four default changes: two reversible with an option, one partly (button size and border, not the glyph), one not configurable.
  • Upgrading is npm install apextree@latest, with react-apextree 2.1.0, vue-apextree 2.1.0 and ngx-apextree 1.2.0.

Which defaults changed?

Default changeReleaseWhat you will seeHow to reverse or upgrade
Motion on first render and reflow2.0.0Nodes grow out of the root. data-x / data-y and a foreignObject's x / y read right after render() return the seed position, not the settled layoutenableAnimation: false, or wait for the springs to settle before measuring
Collapse count inside the button2.0.0The separate badge element is gone; the button widens into a pill that shows the count. collapseBadge* options and --apex-tree-badge-* variables now style the pill, and collapseBadgeFontSize is capped at 70 percent of the button sizeNot configurable. Retarget CSS and tests from the badge element to the button, and remove any spacing you added for the old badge's 27.5px footprint
Redesigned expand/collapse button2.0.0Stroked glyph; expandCollapseButtonSize is 15 (was a hardcoded 14); expandCollapseButtonBorderColor is #E4E7EC (was #BCBCBC); hover uses borderColorHover instead of hardcoded Bootstrap blueexpandCollapseButtonSize: 14, expandCollapseButtonBorderColor: '#BCBCBC'. The old glyph cannot be restored
Collapse zoom cap, maxZoomNodeSpan: 82.0.0Collapsing to a few nodes no longer balloons them to fill the canvasmaxZoomNodeSpan: 0 for the old tight fit, or enableExpandCollapseZoom: false to keep the viewBox fixed

If your code or tests measure layout from the DOM straight after rendering, turn motion off for that instance:

// 1.15: positions were final as soon as render() returned
// 2.0: they are the seed until the springs settle, unless motion is off
new ApexTree(el, { enableAnimation: false }).render(data)

Every other new option is off or neutral by default. Full detail is in Migrating to ApexTree 2.0.

What does the spring motion core change?

Five things animate, all driven by persistent springs rather than keyframes: node position, enter and exit, edge geometry, the camera and the stagger. Each edge is recomputed from its two endpoint springs every frame, so it grows only as its nodes separate. Grouped-leaf trees (groupLeafNodes: true) run through the same engine.

const graph = new ApexTree(el, {
  direction: 'top',
  motion: { spring: 'crisp', stagger: 'wave' },
}).render(data)

motion.spring takes 'crisp' (default), 'gentle' or 'snappy'. motion.stagger: 'wave' (default) unfolds a subtree outward from the node you clicked; 'none' moves everything together.

The tree below is the real ApexTree 2.0. Each button runs one reflow, and clicking a card spotlights its lineage back to the root.

Every button above runs one reflow, so the whole set of nodes springs together in a single wave. Click any card to spotlight its lineage; click it again or press Escape to clear.

Presets and reduced motion are covered in Motion and Animation.

How do you animate a tree between two datasets?

graph.updateData(data) diffs the incoming tree against the live one on id. Nodes in both keep their DOM element and spring to their new position, new ids enter from their parent, and departed ones retract. Collapse state, selection, focus, expanded cards and keyboard focus all survive the update.

const graph = new ApexTree(el, { direction: 'top' }).render(data)

socket.on('org:changed', (next) => graph.updateData(next))

Stable ids are the contract. Build them from your own records (emp-${employee.employeeId}), not from array position (node-${index}), or every node looks new and you get the full redraw you were avoiding.

The Time Travel demo scrubs an org through ten quarters from a headcount chart. The pattern is in Live Data Updates.

Radial and dendrogram layouts

direction: 'radial' puts the root at the centre and each depth on its own ring, and layoutType: 'cluster' pins every leaf to the deepest rank for a true dendrogram. Cluster also works in the cartesian directions, where leaves line up on the bottom row.

new ApexTree(el, {
  direction: 'radial',
  layoutType: 'cluster',
  nodeWidth: 10,
  nodeHeight: 10,
  borderRadius: '50%',
  externalLabel: { enabled: true, fontSize: '10px', collisionStrategy: 'leaves' },
}).render(flareHierarchy)

Radial edges curve around the centre, and external labels fan out along their spoke and flip on the left half so none render upside down. externalLabel.collisionStrategy ('none' | 'hide' | 'leaves') thins colliding labels per ring: 'hide' keeps an evenly spaced subset, and 'leaves' also drops inner-node labels.

See the Radial Dendrogram demo and Radial and Dendrogram Layouts.

Other new features

  • Node sizing: per-node nodeWidth / nodeHeight, and autoNodeHeight measures each card's content (cartesian only). See Node Sizing and Expandable Cards.
  • Expandable cards: graph.toggleCard(id) and cardExpansion.clickToExpand reveal a detail section in place; the card only grows with autoNodeHeight enabled.
  • Focus mode and the active path: graph.focus(id) dims everything outside a node's lineage, and graph.setActivePath(ids) flows a dash along it, styled by edgeFlow. See Focus Mode and the Active Path.
  • Semantic zoom: semanticZoom swaps node content by on-screen width without moving the layout. See Semantic Zoom.
  • Batch verbs: expandAll(), collapseAll(), expandToDepth(n), expandSubtree(id) and collapseSubtree(id), each one reflow.
  • Lazy children: hasChildren: true plus loadChildren(ctx). A rejected promise leaves the node collapsed for a retry; returning [] marks it a leaf. See Lazy Children.
  • Command palette: enableCommandPalette adds a Cmd/Ctrl-K overlay to jump to a node or expand, collapse and fit the tree. See Command Palette.
  • Smaller additions: nodeWrapper(ctx) stamps your own classes and data-* attributes on each node, countBadgeEnabled shows an always-on count, and expandCollapseButtonIconColor and expandCollapseButtonHaloColor theme the button.

Other fixes

  • Wheel zoom and drag pan are no longer swallowed while the camera is moving, and graph.zoom(factor) steps are no longer refused after a programmatic camera move.
  • Radial expand/collapse buttons sit along each node's growth direction instead of at bottom centre, which covered the incoming edge on nodes near the top of the dial.

The full list is in the 2.0.0 GitHub release, and every feature has a runnable example in ApexTree demos.

Upgrading

npm install apextree@latest
  1. Check the four default changes, especially any test that reads node positions right after render().
  2. Bump the framework wrappers to react-apextree 2.1.0, vue-apextree 2.1.0 or ngx-apextree 1.2.0. A data change is now reconciled into the live tree, while an options change still rebuilds it, so keep your options object referentially stable.
  3. Loading from a CDN, bump the version on your link.

ApexTree needs a Pro licence or above whatever the size of your organization. The Community License, free under $2M in annual revenue, budget or funding, does not include it. Without a key, ApexTree renders with a trial watermark. See pricing.

Frequently asked questions

Are there breaking changes in ApexTree 2.0?

No public option or method was removed, and every new option is inert by default. The major version covers four changed defaults: motion on first render, the collapse count drawn inside the button, a redesigned button, and a zoom cap when the camera re-fits after a collapse. Tests that read node positions after render() notice first.

How do I animate an ApexTree between two datasets?

Keep the graph that render() returns and pass each new dataset to graph.updateData(). Matching is by node id, so give every node an id derived from your own records, never from its array position. A renamed or re-parented node with the same id travels to its new place instead of disappearing and reappearing.

Does ApexTree 2.0 support radial layouts and dendrograms?

Yes. Set direction: 'radial' for a tree drawn in rings around the root, and layoutType: 'cluster' to line every leaf up on the deepest rank. The cluster option works in the cartesian directions as well, so a top-down dendrogram puts all leaves on the bottom row.

Do the React, Vue and Angular wrappers support the new API?

Yes, from react-apextree 2.1.0, vue-apextree 2.1.0 and ngx-apextree 1.2.0. Each exposes updateData, the batch verbs, focus, setActivePath, toggleCard, zoom and centerOnNode, plus getGraph() for the rest. Changing the data prop now updates the live tree in place; changing options still rebuilds it.