TanStack
TanStack

Charts

A chart grammar you don't have to outgrow.

TanStack Charts 0.7.2 adds declarative view composition, controlled interactions, motion, spatial layouts, and expanded React Native parity. What changed since 0.6.5.

Docs

Grouped bars

bar

All mark, no chart.

Type Safe & Declarative for both Humans & Agents

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.

account-health.tsx
import { scaleLinear } from '@tanstack/charts-scales/linear'
import { defineChart, dot } from '@tanstack/charts'
import { tooltip } from '@tanstack/charts/tooltip'
import { Chart } from '@tanstack/react-charts'
import { scaleSqrt } from 'd3-scale'

const accountHealth = defineChart({
  marks: [
    dot(accounts, {
      x: 'monthlyRevenue',
      y: 'retention',
      r: 'seats',
      rScale: {
        scale: () => scaleSqrt().range([4, 22]),
      },
      z: 'segment',
      key: 'id',
    }),
  ],
  x: {
    scale: scaleLinear,
    axis: { label: 'Monthly revenue ($k)' },
  },
  y: {
    scale: scaleLinear,
    axis: {
      label: '90-day retention',
      ticks: { format: (value) => percent.format(value) },
    },
  },
  tooltip,
})

export function AccountHealthChart({
  onFocus,
}: {
  onFocus: (account: Account | null) => void
}) {
  return (
    <Chart
      definition={accountHealth}
      ariaLabel="Account health by revenue, retention, segment, and seats"
      onFocusChange={(point) => onFocus(point?.datum ?? null)}
    />
  )
}

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 seats.

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

Monokai

36.73–42.64 KiB across the controlled suite.

The compact React line is 26.48 KiB gzip; its framework-neutral scene is 10.28 KiB. The comparison methodology and fixtures measure complete cold-page browser bundles, including rendering, axes, styles, and library code.

Cold-page bundle comparison

Controlled 12-case cold-page bundles · minified + gzip

TanStack Charts36.742.6 KiB
Chart.js44.758.2 KiB
Observable Plot83.391.9 KiB
Recharts153.1168.3 KiB
Apache ECharts153.1173.2 KiB

Compose from marks to complete views.

Layer marks when they share a coordinate system. For compound charts, composeViews places complete definitions with fill, grid, layer, and inset utilities. Each view keeps its own scales unless you share or align them. View composition reference.

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 { scaleLinear } from '@tanstack/charts-scales/linear'
import { scaleUtc } from 'd3-scale'
import { curveMonotoneX } from 'd3-shape'
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: 'var(--activation-range)',
      fillOpacity: 0.22,
      curve: d3Curve(curveMonotoneX),
    }),
    ruleY([70], {
      id: 'activation-goal',
      stroke: 'var(--activation-goal)',
      strokeOpacity: 0.95,
      strokeWidth: 2,
      strokeDasharray: '7 7',
    }),
    lineY(weeks, {
      id: 'activation-line',
      x: 'date',
      y: 'activation',
      key: 'id',
      stroke: 'var(--activation-line)',
      strokeWidth: 4.25,
      points: true,
      curve: d3Curve(curveMonotoneX),
    }),
    dot(releases, {
      id: 'activation-events',
      x: 'date',
      y: 'activation',
      key: 'id',
      r: 6,
      fill: 'var(--activation-bg)',
      stroke: 'var(--activation-release)',
      strokeWidth: 3,
    }),
    text(releases, {
      id: 'activation-event-labels',
      x: 'date',
      y: 'activation',
      text: 'label',
      key: 'id',
      fill: 'var(--activation-foreground)',
      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.

Partners

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

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