Three releases shipped across ApexTree, ApexGantt and ApexSankey. They read like three small changelogs, but they are mostly one change arriving in three places.

The Apex products grew up as separate libraries that happened to share a name. Each carried its own copy of the license manager, its own theming vocabulary, and its own animation timing. That is invisible right up until you put two of them on one page, at which point you are setting the same license key twice and declaring the same brand colour twice.

These releases end that.

Key takeaways

  • One license key registers across every Apex product on the page. Any product's setLicense licenses all of them.
  • One palette. The --apx-* tokens are honoured family-wide, so a design system declares its colours once on :root.
  • One motion engine. All four products run the same spring driver, with a rest-threshold fix that removes a single-frame snap at the end of short animations.
  • destroy() actually tears down in ApexTree and ApexSankey. Every framework wrapper has been leaking an animation loop on unmount.
  • No API changes. ApexGantt's bundled .d.ts is byte-identical.

One key for the page

A license key was never product-specific. The license payload carries no product field, and never has.

But each library inlined its own private copy of the license manager, so the key lived in a class static that only that bundle could see. Put a chart and a tree on one page and ApexCharts.setLicense licensed the chart while the tree beside it rendered watermarked, which looks exactly like a broken key.

The key, the verdict and the listener set now live in one versioned global slot that all four bundles write to:

import ApexCharts from 'apexcharts'

// licenses the charts, trees, gantt charts and sankey diagrams on this page
ApexCharts.setLicense('your-license-key')

ApexTree.setLicense, ApexGantt.setLicense and ApexSankey.setLicense all do the same thing now. Call whichever is convenient, once, before your first instance is constructed.

To be precise about what this is and is not: it is one call registering the key everywhere. It is not one key unlocking more than you bought. Which entitlements a key grants is still whatever plan it was issued for.

There is also a downgrade guard that came with the shared manager: once a valid key is registered, a later call with an invalid or expired one cannot replace it.

One palette

ApexCharts has read --apx-* design tokens since 6.0. As of these releases, so do the other three.

:root {
  --apx-accent: #4f46e5;   /* primary accent */
  --apx-fore: #1f2937;     /* text and labels */
  --apx-grid: #e5e7eb;     /* gridlines, borders and separators */
  --apx-surface: #ffffff;  /* background surface */
  --apx-series-1: #4f46e5; /* ordered palette for multi-series visuals */
  --apx-series-2: #06b6d4;
}

That is the whole configuration. A dashboard with a chart, a gantt and a sankey on it now takes its palette from one place rather than three, and the tokens are read live, so an OS light/dark flip or a host app swapping its design system is picked up without a re-render.

The resolution order matters, because the tokens sit near the bottom of it:

PrioritySource
1Options you set on the instance
2--apex-tree-* / --apex-gantt-* / --apex-sankey-*
3A registered theme
4--apx-* family tokens
5The built-in default

So adding tokens to :root cannot restyle anything you already themed deliberately. They fill in what you never specified, which is what makes them safe to adopt on an existing app.

One motion engine

All four products now run the spring driver from the shared package rather than each carrying its own.

That is mostly an internal tidy, but one visible thing came out of it. Progress values that run 0..1, the enter/exit reveal in ApexTree and the morph in ApexSankey, had been using rest thresholds meant for SVG coordinate space, where a velocity floor is what decides that a spring has arrived. On a 0..1 range those thresholds are enormous, so the last few percent of every relayout was closed in a single frame. A short fade lost about a tenth of its travel to a snap at the end.

They now use thresholds scaled to their own range. Nothing about the motion was retuned; it just finishes properly.

destroy() finally destroys

This is the fix most people will actually feel, and it is a leak that has been running in production in every framework wrapper.

react-apextree, vue-apextree and ngx-apextree all call tree.destroy() on unmount, and always have. Until 2.1.0 that resolved to the base implementation, which only released the chart context. The spring loop kept ticking, writing data-x, clip-path and viewBox onto elements that were no longer in the document:

// this was always correct, and was always leaking
useEffect(() => {
  const tree = new ApexTree(el.current, options)
  tree.render(data)
  return () => tree.destroy()
}, [])

Destroying a tree mid-collapse leaked a live requestAnimationFrame for as long as its springs took to settle, on every unmount. On a route that mounts and unmounts a tree repeatedly, those accumulate. The keyboard navigator, the semantic-zoom gesture listeners and the pending level-of-detail debounce were never detached at all.

ApexSankey had the same shape of bug: nothing ever tore the renderer down, so a diagram destroyed mid-morph left its relayout driver ticking and redrawing a geometry layer that was no longer in the document.

The wrappers need no change. Upgrade the library and the code you already wrote starts working.

Two smaller things

ApexTree honours prefers-reduced-motion on its own. Nothing ever set the .apextree-reduced-motion class; it was only ever read. So the OS setting was respected only if the embedding app added the class by hand, which in practice meant it usually was not. The tree now watches the media query itself, from the first render and live. A class you set by hand is never taken away, so forcing reduced motion regardless of the OS setting still works.

ApexGantt stops erasing custom properties it does not own. Every render wiped ten --apex-gantt-* properties inline before reading, while only ever writing two. An inline --apex-gantt-bar-fill or --apex-gantt-grid-line you set on the container was silently erased on the next render. Only the two row-background properties the chart actually owns are cleared now, and only while the value sitting there is still the one it wrote.

Upgrading

npm install apextree@latest apexgantt@latest apexsankey@latest

No API changed in any of the three, so there is nothing to migrate. The two behaviour changes, automatic reduced motion in ApexTree and ApexGantt no longer clearing your custom properties, both move in the direction of doing what you already expected.

Docs: ApexTree theming · ApexGantt theming · ApexSankey themes · ApexTree motion

Frequently asked questions

Does one ApexCharts license key cover ApexTree, ApexGantt and ApexSankey?

Yes. A key was never product-specific (the license payload carries no product field), but until these releases each library kept its own private copy of the license manager, so setting a key on one did not license another on the same page. They now share one versioned global slot, so any product's setLicense call licenses all of them. Which entitlements a key grants is still governed by the plan you purchased; this is about a single call registering it everywhere rather than one key unlocking more than you bought.

What are the --apx-* CSS tokens in ApexCharts?

A family-wide set of design tokens: --apx-accent, --apx-fore, --apx-grid, --apx-surface and an ordered --apx-series-N palette. Declare them once on :root and charts, trees, gantt charts and sankey diagrams all follow. They are read live, so an OS light/dark flip or a host app swapping its design system is picked up without re-rendering. They resolve below anything set explicitly, so a chart already themed through its own options or its --apex-<product>-* variables is untouched.

Why was my ApexTree still animating after my React component unmounted?

Because destroy() did not tear down the spring loop before ApexTree 2.1.0. react-apextree, vue-apextree and ngx-apextree all already called tree.destroy() on unmount, but it resolved to the base implementation, which only released the chart context. A tree destroyed mid-collapse left a live requestAnimationFrame writing data-x, clip-path and viewBox onto detached elements for as long as its springs took to settle. The wrappers need no change to pick up the fix; upgrade apextree itself.

Do I need to change any code to upgrade to ApexTree 2.1, ApexGantt 3.18 or ApexSankey 1.12?

No. There is no API change in any of the three; ApexGantt's bundled .d.ts is byte-identical. Two behaviours change on their own: ApexTree now honours prefers-reduced-motion without you wiring it up, and ApexGantt stops clearing --apex-gantt-* custom properties it never set. If you had added the .apextree-reduced-motion class by hand, you can keep it, since a class you set is never removed.

Why did ApexGantt erase my --apex-gantt-* custom properties?

A bug fixed in 3.18.0. Every render cleared ten --apex-gantt-* properties inline before reading them, while the chart only ever wrote two. An inline --apex-gantt-bar-fill or --apex-gantt-grid-line set on the container was therefore erased on the next render. Now only the two row-background properties the chart actually owns are cleared, and only while the value sitting there is still the one it wrote.