What's New in ApexTree 2.0
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'andlayoutType: '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 change | Release | What you will see | How to reverse or upgrade |
|---|---|---|---|
| Motion on first render and reflow | 2.0.0 | Nodes 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 layout | enableAnimation: false, or wait for the springs to settle before measuring |
| Collapse count inside the button | 2.0.0 | The 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 size | Not 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 button | 2.0.0 | Stroked glyph; expandCollapseButtonSize is 15 (was a hardcoded 14); expandCollapseButtonBorderColor is #E4E7EC (was #BCBCBC); hover uses borderColorHover instead of hardcoded Bootstrap blue | expandCollapseButtonSize: 14, expandCollapseButtonBorderColor: '#BCBCBC'. The old glyph cannot be restored |
Collapse zoom cap, maxZoomNodeSpan: 8 | 2.0.0 | Collapsing to a few nodes no longer balloons them to fill the canvas | maxZoomNodeSpan: 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, andautoNodeHeightmeasures each card's content (cartesian only). See Node Sizing and Expandable Cards. - Expandable cards:
graph.toggleCard(id)andcardExpansion.clickToExpandreveal a detail section in place; the card only grows withautoNodeHeightenabled. - Focus mode and the active path:
graph.focus(id)dims everything outside a node's lineage, andgraph.setActivePath(ids)flows a dash along it, styled byedgeFlow. See Focus Mode and the Active Path. - Semantic zoom:
semanticZoomswaps node content by on-screen width without moving the layout. See Semantic Zoom. - Batch verbs:
expandAll(),collapseAll(),expandToDepth(n),expandSubtree(id)andcollapseSubtree(id), each one reflow. - Lazy children:
hasChildren: trueplusloadChildren(ctx). A rejected promise leaves the node collapsed for a retry; returning[]marks it a leaf. See Lazy Children. - Command palette:
enableCommandPaletteadds 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 anddata-*attributes on each node,countBadgeEnabledshows an always-on count, andexpandCollapseButtonIconColorandexpandCollapseButtonHaloColortheme 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
- Check the four default changes, especially any test that reads node positions right after
render(). - Bump the framework wrappers to react-apextree 2.1.0, vue-apextree 2.1.0 or ngx-apextree 1.2.0. A
datachange is now reconciled into the live tree, while anoptionschange still rebuilds it, so keep youroptionsobject referentially stable. - 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.