TanStack
TanStack

Charts

A chart grammar you don't have to outgrow.

TanStack Charts 0.3.1 is on npm. A compact React line consumer is 16.48 KiB gzip; its framework-neutral scene is 8.12 KiB.

Docs
Data
productSignals · 8 rows
Marks
areaY + ruleY + lineY + dot + text
Scales
linear × linear
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]),
    axis: { ticks: { count: 4, format: (month) => `M${month}` } },
  },
  y: {
    scale: scaleLinear().domain([30, 100]),
    axis: { ticks: { count: 4 } },
    grid: true,
  },
  theme: kineticDarkTheme,
})

Examples, not presets.

Browse all examples
Apple stock line with seasonal gaps
Industry unemployment with end labels
San Francisco temperature range band
Industry unemployment stacked area
Sorted vertical bars
Horizontal ranking with long labels
Grouped bars
Stacked bars
Bubble scatterplot
Histogram
Labeled ordinal heatmap
Anscombe quartet small multiples
Open-to-close price intervals
Point estimates with error bars
Grouped boxplot
Ranked lollipop chart
Dumbbell comparison
Cumulative histogram
San Francisco temperature moving averages
Industry share of unemployment
Streamgraph
Bollinger band
Quantitative binned heatmap
Calendar heatmap
Diverging Likert responses
Parallel coordinates
Candlestick chart
Annual gasoline-price waterfall
Two-period slopegraph
Scatterplot with linear regression
Metro inequality change arrows
Apple close versus moving average
Pointer-selected tooltip
Grouped X tooltip
Tidy hierarchy tree
Delaunay spatial network
Filled wind-speed contours
Point density contours
Westport House floor plan
Les Misérables character network
English letter frequency waffle
Two-dimensional vector field
Hexagonally binned density
Framed guide-free scatterplot
Empirical cumulative distribution
Faceted distributions
Beeswarm distribution
Log-scale Flare class sizes
Industry unemployment bump chart
Indexed industry unemployment
Directed connected scatterplot
Scatterplot with marginal histograms
Annotated AAPL extrema
Mean penguin mass reducer bars
AAPL lag-one autocorrelation
Industry unemployment percentile ribbon
Ridgeline density comparison
Violin distribution comparison
Marimekko survey composition
Voronoi nearest-point interaction
Layered Seattle weather chart
Palmer penguins by species and sex
Stacked and adjacent Seattle weather bars
Automobile specifications scatter
Flare analytics treemap
Simple radar chart
English letter frequency pie
English letter frequency donut
Survey agreement share gauge
Snapped axis pointer with grouped tooltip
Interactive series legend
Linked chart and data table selection
Focus and context time window
Pinned penguin tooltip with a nested chart
Scrollable resource timeline lanes
Streaming window preservation
Synchronized cursors across views
Free cursor over car measurements
Observed monthly AAPL brush selection
Wheel zoom and pan over AAPL closes
AAPL close playback scrubber
Editable event range
Letter frequency pie with labels
Letter frequency donut with total
Rounded letter frequency donut
Nested Flare package sizes
English letter frequency rose
County unemployment gauge
Comparative radar chart
Concentric letter frequency bars
Flare analytics sunburst
World learning-poverty choropleth
World population bubble map
Orthographic globe with graticule
HMS Beagle voyage
Seattle daily high-temperature polar line
Surface wind polar scatter
World population-density choropleth
United States county unemployment choropleth
Standard world projection gallery
Basic Sankey
Sankey

Agents write the chart. TypeScript checks the result.

Every example compiles under strict TypeScript. Fields, datum types, inferred domains and keys, tooltips, and focus callbacks stay connected to the source datum; the type suite rejects invalid definitions.

Example prompt

Plot monthly revenue against retention. Size each account by seats, color it by segment, add tooltips, and keep the Account type in every callback.

Account → SVG
dot(accounts, {
 x: 'monthlyRevenue',
 y: 'retention',
 r: 'seats',
 z: 'segment',
})

Account health

Example account dataset · Q2 2026

Which high-revenue accounts need retention attention?

EnterpriseGrowthSMBBubble size = seats
Illustrative account data. Horizontal position shows monthly revenue in thousands of dollars, vertical position shows 90-day retention, color distinguishes SMB, Growth, and Enterprise segments, and bubble area represents account count.

Make 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

16.48 KiB for a compact React line.

The framework-neutral scene is 8.12 KiB and retains neither D3 nor InternMap. For an apples-to-apples comparison, the pinned 12-case suite measures full cold-page browser bundles across line, bar, area, and scatter × basic, interactive, and advanced; TanStack spans 26.58–32.08 KiB gzip.

Gzip bundle comparison

Controlled cold-page ranges and main-export snapshots*

  1. TanStack Charts26.58–32.08 KiB
  2. visx*49 KB
  3. Chart.js44.70–58.21 KiB
  4. Lightweight Charts*60 KB
  5. Vega-Lite*87 KB
  6. Observable Plot83.34–91.94 KiB
  7. D3*90 KB
  8. Highcharts*100 KB
  9. Victory*105 KB
  10. Nivo*143 KB
  11. Recharts153.08–168.27 KiB
  12. Apache ECharts153.10–173.18 KiB
  13. ApexCharts*164 KB
  14. Plotly.js*~250 kB partial
  15. AG Charts*367 KB
0100200300400 KB

Controlled ranges use the tracked v0.3.1 baseline of full cold-page bundles with pinned package versions.

* Main-export snapshots use different methodology; modular imports can be smaller. visx is @visx/xychart. Vega-Lite excludes the Vega runtime. Plotly.js shows its partial build; the full build is ~3.6 MB min+gzip. Sources and caveats.

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

ActivationExpected rangeGoalReleases
Illustrative weekly activation data from January through May 2026. The actual rate rises from 48 to 78 percent, compared with an expected range and a 70 percent goal. Onboarding v2 and Invite flow are marked as release events.
activation-chart.ts
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]),
    axis: {
      label: 'Week ending',
      ticks: { format: (value) => monthDay.format(value) },
    },
    grid: false,
  },
  y: {
    scale: scaleLinear().domain([40, 82]),
    axis: {
      label: 'Activation rate (%)',
      ticks: { count: 5, format: (value) => `${Math.round(value)}%` },
    },
    grid: true,
  },
  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 examples

Partners

Gold
Lovable
CodeRabbit
Cloudflare
Netlify
Railway
Silver
OpenRouter
WorkOS
AG Grid
SerpApi
Clerk
Bronze
Sentry
Prisma
Unkey
Electric
OSS Sponsors

Sponsors get special perks like private discord channels, priority issue requests, and direct support!