Charts
A chart grammar you don't have to outgrow.
Build the chart you need from typed data, marks, channels, and D3 scales—then render responsive SVG in React or vanilla TypeScript.
const releases = productSignals.filter(
(row) => row.product === 'Form' || row.product === 'Store',
)
export const kineticLayeredChart = defineChart({
marks: [
areaY(productSignals, {
id: 'hero-area',
x: 'month',
y1: 'previous',
y2: 'forecast',
key: 'id',
fill: '#3aa3c4',
fillOpacity: 0.24,
curve: d3Curve(curveMonotoneX),
}),
ruleY([70], {
id: 'hero-rule',
stroke: '#e06e49',
strokeWidth: 2,
strokeDasharray: '7 7',
}),
lineY(productSignals, {
id: 'hero-line',
x: 'month',
y: 'value',
key: 'id',
stroke: '#61adbf',
strokeWidth: 3,
curve: d3Curve(curveMonotoneX),
}),
dot(productSignals, {
id: 'hero-points',
x: 'month',
y: 'value',
z: 'segment',
key: 'id',
stroke: '#9cd5e2',
strokeWidth: 2,
r: 4,
}),
text(releases, {
id: 'hero-labels',
x: 'month',
y: 'value',
text: 'product',
key: 'id',
fill: '#ffffff',
fontSize: 11,
fontWeight: 650,
dy: -18,
}),
],
x: {
scale: scaleLinear().domain([1, 8]),
ticks: 4,
format: (month) => `M${month}`,
},
y: {
scale: scaleLinear().domain([30, 100]),
ticks: 4,
grid: true,
},
theme: kineticDarkTheme,
})Examples, not presets.
Browse all examplesMake it look like your product.
Same data and scales. CSS variables, themes, mark props, custom tooltips, or your own renderer control the rest.
Editorial
Product
Terminal
Complete SVG charts. 18.5–19.2 kB gzip.
Measured minified and gzipped from one-series charts using the vanilla renderer. Granular imports leave unrelated marks and D3 modules out.
Common chart bundles
Minified + gzip
Weekly revenue by product
One definition · two container sizes
Layer marks over shared scales.
Area, rules, lines, points, and labels share one coordinate system. The definition beside the chart is the whole composition.
Weekly activation rate
Jan–May 2026 · illustrative product telemetry
78%
above 70% goal
import { curveMonotoneX, scaleLinear, scaleUtc } from 'd3'
import {
areaY,
d3Curve,
defineChart,
dot,
lineY,
ruleY,
text,
} from '@tanstack/charts'
import { releases, weeks } from './activation-data'
import { activationTheme } from './activation-theme'
const monthDay = new Intl.DateTimeFormat('en-US', {
day: 'numeric',
month: 'short',
timeZone: 'UTC',
})
export const activationChart = defineChart({
marks: [
areaY(weeks, {
id: 'activation-range',
x: 'date',
y1: 'expectedLow',
y2: 'expectedHigh',
key: 'id',
fill: '#3aa3c4',
fillOpacity: 0.2,
curve: d3Curve(curveMonotoneX),
}),
ruleY([70], {
id: 'activation-goal',
stroke: '#e06e49',
strokeOpacity: 0.95,
strokeWidth: 2,
strokeDasharray: '7 7',
}),
lineY(weeks, {
id: 'activation-line',
x: 'date',
y: 'activation',
key: 'id',
stroke: '#61adbf',
strokeWidth: 3.25,
points: true,
curve: d3Curve(curveMonotoneX),
}),
dot(releases, {
id: 'activation-events',
x: 'date',
y: 'activation',
key: 'id',
r: 6,
fill: '#ffffff',
stroke: '#3aa3c4',
strokeWidth: 3,
}),
text(releases, {
id: 'activation-event-labels',
x: 'date',
y: 'activation',
text: 'label',
key: 'id',
fill: '#ffffff',
fontSize: 12,
fontWeight: 650,
dy: -21,
}),
],
x: {
scale: scaleUtc().domain([weeks[0]!.date, weeks.at(-1)!.date]),
label: 'Week ending',
format: (value) => monthDay.format(value),
grid: false,
},
y: {
scale: scaleLinear().domain([40, 82]),
label: 'Activation rate (%)',
format: (value) => `${Math.round(value)}%`,
grid: true,
ticks: 5,
},
theme: activationTheme,
})TanStack Charts builds on Leland Wilkinson's grammar of graphics and the work of ggplot2, Vega-Lite, and Observable Plot. Its marks-and-channels API is most directly inspired by Observable Plot, but the runtime is an independent implementation.
Choose the chart by the question you're answering.
All 100 examplesTime
How did it change over time?
Compare
How do values compare?
Composition
What makes up the total?
Distribution
How are values distributed?
Relationship
How do variables relate?
Range
What is the range or duration?
Connection
How is it connected?
Location
How does it vary by location?
Partners
Sponsors get special perks like private discord channels, priority issue requests, and direct support!