Migration is a semantic exercise, not a component-name translation. First describe what the existing chart means and how users operate it. Then express that behavior with data preparation, scales, marks, and host options.
Current definitions put Cartesian scale and axis options under scales:
const definition = defineChart({
marks,
scales: {
x: { scale: xScale },
y: { scale: yScale, grid: true },
},
})The earlier root properties still work temporarily:
const legacyDefinition = defineChart({
marks,
x: { scale: xScale },
y: { scale: yScale, grid: true },
})Development builds warn with the exact replacement. Move x to scales.x and y to scales.y. Root x and y will be removed when TanStack Charts enters Alpha. New code and generated code should only use the scales form.
Polar definitions follow the same migration. Move polar({ angle, radius }) to polar({ scales: { angle, radius } }). If neither positional scale is used, write scales: { angle: null, radius: null }. The old polar properties also work temporarily, warn in development, and will be removed when TanStack Charts enters Alpha.
During this compatibility window, ChartSpec is a union of the canonical and legacy shapes. If application code extended the old interface, replace that extension with an intersection:
import type { ChartSpec } from '@tanstack/charts'
type ApplicationChartSpec = ChartSpec & {
applicationTag: string
}Record:
Screenshots alone do not capture these decisions.
Keep proven application, server, SQL, or D3 transforms for the first migration. Pass their output to marks directly. Rewriting analytical logic at the same time makes it difficult to tell whether a visual difference is a renderer regression or a changed calculation.
Move or simplify transforms only after parity is measured. The dependency boundary is explained in Scales.
Map each visible layer independently:
Then assign explicit scales and guides. Complex charts are usually several ordinary marks sharing a coordinate system, not one specialized chart type.
Install @tanstack/charts once and keep the framework peers and D3 modules that application source imports directly. Move TanStack scale and adapter imports to package subpaths:
| Previous import | Current import |
|---|---|
| @tanstack/charts-scales/<family> | @tanstack/charts/scales/<family> |
| @tanstack/react-charts | @tanstack/charts/react |
| @tanstack/react-charts/<capability> | @tanstack/charts/react/<capability> |
| @tanstack/react-native-charts | @tanstack/charts/react-native |
| @tanstack/react-native-charts/tooltip | @tanstack/charts/react-native/tooltip |
| @tanstack/octane-charts | @tanstack/charts/octane |
| @tanstack/octane-charts/<capability> | @tanstack/charts/octane/<capability> |
| @tanstack/<framework>-charts | @tanstack/charts/<framework> |
The same mapping applies to React and Octane /core entries and React /tooltip. Exact ESM entry graphs and sideEffects: false preserve capability and framework tree shaking inside the single published package.
For the current breaking API:
See Marks and Layering and the Example Gallery.
Use the same frozen data and dimensions on both implementations. Compare:
Prefer numeric and behavioral assertions. Use screenshot diffs for the remaining painted details.
A reliable order is:
Keep a temporary renderer switch only as a migration verification tool with a defined removal gate. It should not become permanent application architecture.
Do not preserve accidental internals:
Preserve user-visible meaning and behavior. Replace implementation accidents with the documented TanStack Charts boundary.
Before deleting the old path: