Animations

ApexCharts provides a smooth experience with the help of SVG.js's built-in animations. Select a chart type and click Update to see the animation in action.

Demo not moving? Check your reduced-motion setting

Since 5.12, a chart skips its animations when the operating system asks for reduced motion, so the demo above renders instantly instead of drawing in. Run this in the console to find out which side of it you are on:

matchMedia('(prefers-reduced-motion: reduce)').matches

true means the preference is on and the chart is behaving as designed. To animate anyway, opt out on the chart:

chart: { animations: { respectReducedMotion: false } }

Reduced motion covers where the setting lives and what turns it on without anyone being asked.

To see all options available in animations, please refer to Animations configuration.

Reduced Motion

ApexCharts skips animations entirely when the operating system asks for reduced motion. This is deliberate and on by default since 5.12, and it is what WCAG 2.3.3 asks of any interface that moves on its own.

While (prefers-reduced-motion: reduce) matches, chart.animations.respectReducedMotion forces both of these off for as long as the preference is on:

  • chart.animations.enabled, the initial mount draw
  • chart.animations.dynamicAnimation.enabled, the data-update transitions

Because both go, the symptom is "nothing animates at all" rather than "animations look different". The chart still renders in full and every value is correct. It simply arrives in its final state on the first frame, which is why a live demo can look frozen.

Charts running 5.11 or earlier ignore the preference entirely and animate regardless.

Am I in reduced motion?

matchMedia('(prefers-reduced-motion: reduce)').matches

true means the preference is on, and a chart that renders without animating is correct behaviour rather than a bug.

Where the preference comes from

SourceWhere it lives
macOSSystem Settings > Accessibility > Display > Reduce motion
Windows 11Settings > Accessibility > Visual effects > Animation effects
Windows Battery SaverForces the preference on automatically while it is active
Remote desktop and VDIMost sessions report it whether or not anyone chose it

The last two matter most for diagnosis, because they turn the preference on without anyone asking for it. That is how a developer ends up with charts that never animate on their desktop while the same page animates normally on their phone.

Opting out

The default is the right one for most sites, so treat this as a decision rather than a fix. A kiosk display, a screen-recording rig or a QA harness is a fair reason to animate anyway:

chart: {
  animations: {
    respectReducedMotion: false,
  },
}

From 7.6.0 this covers the whole chart. The animations tweened in JavaScript come back (the initial mount draw, the gradual reveal, data-update transitions and chart-type morph), and so do the ones driven by CSS: the pie and donut slice-offset slide, the drilldown loading spinner, the tooltip and crosshair fades, and the data-label fade on a collapsing series. Internally the option puts an apexcharts-ignore-reduced-motion class on the chart's canvas, which is what the stylesheet's reduced-motion rules are scoped against.

On 7.5.1 and earlier the flag reached only the JavaScript. The stylesheet carried its own unconditional @media (prefers-reduced-motion: reduce) block flattening every duration inside .apexcharts-canvas with !important, and no JavaScript flag can reach a CSS rule, so the CSS-driven transitions above stayed instant however the option was set. Upgrading is the fix. Overriding it from a page means out-specifying an !important rule on every element that moves, which is fragile and easy to get half right.

Custom Easing (Cadence)

The animations.easing option accepts a named curve, a cubic-bezier array, or a function, so you can tune how the generic tweens feel without touching anything else:

const options = {
  chart: { animations: { easing: 'easeOutBack' } }, // or [0.34, 1.56, 0.64, 1], or (t) => t * t
}

Register your own named curve with ApexCharts.registerEasing('bounce', (t) => /* ... */). The default (easeInOutSine) is unchanged, so existing charts animate exactly as before, and the tuned initial-draw easings (pen-stroke, grow, pop) stay fixed. A separate data-change override is available via dynamicAnimation.easing; see chart.animations.

Easing:

Pick a curve, then Shuffle data to watch the bars tween to their new heights with it. Set it via chart.animations.easing: a registered name, a cubic-bezier array, or a function. The default, easeInOutSine, leaves existing charts animating exactly as before.

Morphing Between Chart Types

When updateOptions changes chart.type, ApexCharts can tween from the old shape to the new one instead of destroying and rebuilding the chart. The morph is an optional, tree-shakeable feature, so it must be imported before it takes effect:

import ApexCharts from 'apexcharts'
import 'apexcharts/features/morph'

Then enable it under chart.animations:


chart: {
  animations: {
    chartTypeMorph: {
      enabled: true,
      speed: 600,
    },
  },
}

Supported transitions include bar ↔ pie / donut / radialBar / polarArea / funnel / pyramid / gauge, plus the trivial pie ↔ donut ↔ polarArea cases. When the source and target types (or their data shapes) are incompatible, the chart falls back to an instant snap.

Morph speed700 ms

Switch types to watch supported pairs tween between shapes. Enable it with the tree-shakeable apexcharts/features/morph import and chart.animations.chartTypeMorph. Incompatible pairs fall back to an instant snap.

Conserving the Ink

A morph between one mark and many objects, a bar becoming the dots it was counting, does not crossfade. Frame by frame a crossfade is a double exposure: two pictures both half-visible, neither becoming the other, which is why easing never made it read as one shape turning into another.

Instead the mark is cut into exactly as many cells as there are objects, and every cell flies to its own object, corners rounding off and fill blending on the way. A summary mark is cut along its own silhouette rather than its bounding box, and a wedge along its curve, which is how a donut's hole survives being taken apart.

This is what rowSeries() is for: it hands back the rows a mark stands for, so switching type opens the summary into its own observations rather than swapping in an unrelated chart.


chart.updateOptions({
  chart: { type: 'unit' },
  series: chart.rowSeries()
})

Any two mark families pair. The engine used to decline combinations simply because nobody had exercised them. The only pairing still closed is a dot cluster against a partition, where a divider has no cut for a tile or an arc and the result would fall back to a fade. Treemap and sunburst pair at every level rather than only the leaves, and a box plot unfolds into its violin and folds back.