TanStack
Guides

Dynamic Data and Animation

Definition identity is the application update boundary. A framework memo captures the current data and options; Charts rebuilds the scene when that definition changes or when the chart surface changes size.

React

tsx
function RankingChart({ rows, metric, accent }: Props) {
  const definition = useMemo(() => {
    const ranked = rankRows(rows, metric)

    return defineChart({
      animate: { duration: 280, easing: 'ease-out' },
      chart: ({ width }) => ({
        marks: [
          barX(ranked, {
            x: 'value',
            y: 'label',
            fill: accent,
          }),
        ],
        x: {
          scale: scaleLinear,
          nice: true,
          axis: { ticks: { count: width < 420 ? 4 : 7 } },
        },
        y: {
          scale: () => scaleBand<string>().padding(0.1),
        },
      }),
    })
  }, [rows, metric, accent])

  return <Chart definition={definition} ariaLabel="Revenue ranking" />
}

Use the framework's native equivalent: computed, createMemo, $derived, Angular computed, or Octane useMemo.

Vanilla

ts
host.update({
  ...options,
  definition: createRankingDefinition(nextRows, nextMetric, nextAccent),
})

Stable keys

Built-in marks first use an explicit key, then a unique datum.id, then a unique nested datum.data.id. Marks can also infer identity from semantic positional channels:

ts
barX(rows, {
  x: 'value',
  y: 'label',
})

Here barX uses the unique y value when rows have no id. barY uses x; lineY and areaY use x; areaX uses y; rects and cells use their x/y interval tuple. Dots and text try x, then y, then their x/y tuple. Inference is scoped to each group and falls back to array position when a candidate is incomplete or duplicated. Development builds warn once for each affected mark instance.

Supply key when the inferred value is not the entity's identity or can change independently of it. Stable identity preserves surviving SVG elements, focused points, and transition continuity.

Lightweight SVG tweening

animate accepts true or:

  • duration: milliseconds;
  • easing: linear, ease, ease-in, ease-out, ease-in-out, or a function from normalized progress to normalized progress;
  • respectReducedMotion: defaults to true;
  • resize: defaults to false, so responsive relayout does not repeatedly restart animation.

Numeric geometry and compatible path data interpolate. Entering and exiting nodes reconcile by key. If an update interrupts a transition, it begins from the geometry currently painted on screen.

Static SVG, server rendering, and createChartScene do not include animation.

Use this path when a small default-SVG tween is enough. It has no spring solver, definition-local timing cascade, entrance choreography, or retained velocity.

Optional tween and spring motion

Use motion() when animation quality is part of the chart contract:

ts
import { motion } from '@tanstack/charts/motion'
import { mountChartRenderer } from '@tanstack/charts/renderer'

const definition = defineChart({
  motion: {
    transition: { type: 'spring', stiffness: 170, damping: 18, mass: 1 },
  },
  marks: [
    lineY(rows, {
      x: 'date',
      y: 'actual',
      key: 'id',
    }),
    lineY(rows, {
      x: 'date',
      y: 'forecast',
      key: 'id',
      motion: { transition: { type: 'spring', mass: 1.25 } },
    }),
  ],
  x: { scale: scaleUtc },
  y: { scale: scaleLinear },
})

const host = mountChartRenderer(container, {
  definition,
  renderer: motion(),
  width: 640,
  height: 360,
  ariaLabel: 'Actual and forecast revenue',
})

Each host has one animation owner. The default SVG renderer uses animate. When motion() is the renderer, it ignores animate and uses motion declarations from the definition. A motion declaration configures the motion renderer; it does not select it.

Motion declarations can live on the chart, a mark, an axis, its ticks, tick labels, or axis label. A callback can specialize enter, update, and exit by series or datum:

ts
barY(rows, {
  x: 'month',
  y: 'revenue',
  key: 'id',
  motion(context) {
    if (context.phase === 'enter') {
      return { delay: context.datumIndex * 35 }
    }
    if (context.datum?.status === 'forecast') {
      return { transition: { type: 'tween', duration: 240 } }
    }
  },
})

Same-type partial transitions inherit omitted properties. Springs use physical stiffness, damping, and mass; duration is not a spring parameter. Rapid retargeting preserves the currently painted value and velocity. Keyed interaction points move with the presentation geometry rather than jumping to the next scene.

Spring updates always retarget immediately; a returned update delay is ignored so incoming momentum cannot freeze. Use delays for spring enter/exit choreography or any tween phase.

The renderer grows entering bars from their semantic baseline, reveals entering line groups, staggers bar entrances, morphs compatible numeric SVG geometry, and fades incompatible keyed topology. Authored delay replaces automatic staggering for that target.

motion() respects reduced motion and does not animate resize-only updates by default. It adopts server-rendered SVG without replaying entrance motion. Static SVG and Canvas accept the same definitions but paint the final state.

See the Motion reference for the complete cascade, types, focus-state transitions, compatibility limits, and standalone spring sampler.

Streaming

For high-rate data:

  1. Keep source history outside the chart if the product needs it.
  2. Capture a bounded visible window or encoded representation.
  3. Preserve keys for rows that survive the roll.
  4. Keep viewport state controlled.
  5. Coalesce upstream work when only the latest state matters.

The final definition passed to host.update is applied synchronously.