Recharts vs Chart.js vs Nivo 2026: React Charts Compared
TL;DR
For most React dashboards: Recharts is the pragmatic choice — declarative JSX API, solid TypeScript types, and common chart types covered without much boilerplate. Chart.js (via react-chartjs-2) is the battle-tested option for vanilla JS or when you need maximum chart variety. Nivo gives you beautiful, accessible defaults at the cost of bundle size. Visx (Airbnb, powered by D3) is for teams that need maximum control and are willing to compose primitives from scratch.
Key Takeaways
- Recharts: ~1.8M weekly downloads — declarative, React-first, reasonable bundle size
- Chart.js: ~4.1M weekly downloads — most popular JS charting library overall, non-React-native
- Nivo: ~450K weekly downloads — beautiful defaults, SVG + Canvas, great accessibility
- Visx: ~300K weekly downloads — D3-powered primitives, maximum flexibility, steep learning curve
- Recharts wins for straightforward dashboards with standard chart types
- Visx wins when you need highly custom, interactive data visualizations
- Chart.js's high downloads are largely non-React usage
Download Trends
| Package | Weekly Downloads | Rendering | Bundle Size |
|---|---|---|---|
chart.js | ~4.1M | Canvas | ~65KB |
react-chartjs-2 | ~1.6M | Canvas (via Chart.js) | ~1KB (wrapper) |
recharts | ~1.8M | SVG | ~370KB |
@nivo/core | ~450K | SVG/Canvas | ~40KB core |
@visx/shape | ~300K | SVG (D3) | Modular |
Rendering: SVG vs Canvas
SVG (Recharts, Nivo, Visx):
- CSS-styleable — inspect elements, apply classes
- Accessible — screen reader support, ARIA attributes
- Scales infinitely without pixelation
- Slower for large datasets (thousands of data points)
Canvas (Chart.js):
- Faster rendering for large datasets
- Not accessible by default (single
<canvas>element) - Can't style individual elements with CSS
- Renders sharper at high DPI
Recharts
Recharts was specifically designed for React — charts are JSX components, not configuration objects:
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts"
const downloadData = [
{ month: "Oct", recharts: 1400, nivo: 280, visx: 180 },
{ month: "Nov", recharts: 1500, nivo: 310, visx: 195 },
{ month: "Dec", recharts: 1650, nivo: 340, visx: 215 },
{ month: "Jan", recharts: 1700, nivo: 390, visx: 240 },
{ month: "Feb", recharts: 1800, nivo: 450, visx: 300 },
]
function DownloadChart() {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={downloadData} margin={{ top: 5, right: 30, bottom: 5, left: 0 }}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip formatter={(value) => `${value.toLocaleString()}/wk`} />
<Legend />
<Line type="monotone" dataKey="recharts" stroke="#8884d8" strokeWidth={2} dot={false} />
<Line type="monotone" dataKey="nivo" stroke="#82ca9d" strokeWidth={2} dot={false} />
<Line type="monotone" dataKey="visx" stroke="#ffc658" strokeWidth={2} dot={false} />
</LineChart>
</ResponsiveContainer>
)
}
Custom Recharts tooltip:
const CustomTooltip = ({ active, payload, label }: TooltipProps<number, string>) => {
if (!active || !payload?.length) return null
return (
<div className="bg-white border rounded shadow p-3">
<p className="font-semibold">{label}</p>
{payload.map((entry) => (
<p key={entry.dataKey} style={{ color: entry.color }}>
{entry.name}: {entry.value?.toLocaleString()}/wk
</p>
))}
</div>
)
}
Recharts chart types: Line, Bar, Area, Pie, Radar, Scatter, Treemap, Funnel, RadialBar
Recharts limitations:
- Bundle is large (~370KB) — includes everything
- Complex composited charts can get verbose
- Animation customization is limited
Chart.js via react-chartjs-2
Chart.js is the most widely used JavaScript charting library, with react-chartjs-2 as the thin React wrapper:
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
} from "chart.js"
import { Line } from "react-chartjs-2"
// Must register components (tree-shaking):
ChartJS.register(
CategoryScale, LinearScale, PointElement, LineElement,
Title, Tooltip, Legend
)
const data = {
labels: ["Oct", "Nov", "Dec", "Jan", "Feb"],
datasets: [
{
label: "Recharts",
data: [1400, 1500, 1650, 1700, 1800],
borderColor: "rgb(136, 132, 216)",
backgroundColor: "rgba(136, 132, 216, 0.1)",
tension: 0.4,
},
],
}
const options = {
responsive: true,
plugins: {
legend: { position: "top" as const },
title: { display: true, text: "Weekly Downloads" },
},
scales: {
y: { beginAtZero: false },
},
}
function ChartJSExample() {
return <Line data={data} options={options} />
}
Chart.js strengths:
- 30+ chart types including specialized charts (bubble, polar area, financial)
- Canvas rendering handles 10,000+ data points
- Extensive plugin ecosystem (zoom, annotation, financial)
- Configuration-based — easy to understand and modify
Chart.js for React limitations:
- Configuration object, not JSX — less React-idiomatic
- TypeScript types require separate
@types/chart.js(v3+ has built-in types) - react-chartjs-2 adds a React-specific API layer that can feel disconnected
- Animation on data updates requires careful ref management
Nivo
Nivo provides complete, production-ready chart components with beautiful defaults:
import { ResponsiveLine } from "@nivo/line"
const nivoData = [
{
id: "recharts",
color: "hsl(248, 70%, 50%)",
data: [
{ x: "Oct", y: 1400 },
{ x: "Nov", y: 1500 },
{ x: "Dec", y: 1650 },
{ x: "Jan", y: 1700 },
{ x: "Feb", y: 1800 },
],
},
]
function NivoChart() {
return (
<div style={{ height: 300 }}>
<ResponsiveLine
data={nivoData}
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
xScale={{ type: "point" }}
yScale={{ type: "linear", min: "auto", max: "auto" }}
curve="cardinal"
axisBottom={{ legend: "Month", legendOffset: 36 }}
axisLeft={{ legend: "Downloads", legendOffset: -40 }}
pointSize={8}
useMesh={true} // Performance optimization for hover
legends={[{
anchor: "bottom-right",
direction: "column",
itemWidth: 100,
itemHeight: 20,
}]}
/>
</div>
)
}
Nivo's standout features:
// Built-in canvas mode for large datasets:
import { ResponsiveLineCanvas } from "@nivo/line"
// Same API as SVG version, renders via Canvas for performance
// Motion/animation — all charts have spring animations by default
// Accessibility — ARIA labels, keyboard navigation built-in
// Available chart types (all with ResponsiveXxx wrappers):
// Bar, Line, Pie, Scatter, HeatMap, Treemap, Sankey, Chord,
// Calendar, Waffle, Bump, Stream, SwarmPlot, CirclePacking, and more
Nivo limitations:
- Per-chart packages —
@nivo/line,@nivo/bar, etc. add up in bundle size - Less low-level control than Visx
- Config-heavy — more props than Recharts
Visx
Visx (by Airbnb) gives you D3's power through React components — low-level primitives to compose any chart:
import { LinePath, AreaClosed, Bar } from "@visx/shape"
import { Group } from "@visx/group"
import { scaleLinear, scaleBand, scaleTime } from "@visx/scale"
import { AxisBottom, AxisLeft } from "@visx/axis"
import { GridRows, GridColumns } from "@visx/grid"
import { Tooltip, useTooltip, TooltipWithBounds } from "@visx/tooltip"
import { localPoint } from "@visx/event"
import { curveMonotoneX } from "@visx/curve"
interface DataPoint {
date: Date
value: number
}
interface LineChartProps {
data: DataPoint[]
width: number
height: number
margin?: { top: number; right: number; bottom: number; left: number }
}
function VisxLineChart({ data, width, height, margin = { top: 20, right: 20, bottom: 40, left: 50 } }: LineChartProps) {
const innerWidth = width - margin.left - margin.right
const innerHeight = height - margin.top - margin.bottom
// Define scales manually — full D3 control:
const xScale = scaleTime({
range: [0, innerWidth],
domain: [Math.min(...data.map(d => d.date.getTime())), Math.max(...data.map(d => d.date.getTime()))],
})
const yScale = scaleLinear({
range: [innerHeight, 0],
domain: [0, Math.max(...data.map(d => d.value))],
nice: true,
})
const { tooltipData, tooltipLeft, tooltipTop, showTooltip, hideTooltip } = useTooltip<DataPoint>()
return (
<svg width={width} height={height}>
<Group left={margin.left} top={margin.top}>
<GridRows scale={yScale} width={innerWidth} stroke="#e0e0e0" />
<GridColumns scale={xScale} height={innerHeight} stroke="#e0e0e0" />
<LinePath
data={data}
x={(d) => xScale(d.date) ?? 0}
y={(d) => yScale(d.value) ?? 0}
stroke="#8884d8"
strokeWidth={2}
curve={curveMonotoneX}
/>
{/* Invisible hover bars for tooltip capture */}
{data.map((d, i) => (
<Bar
key={i}
x={xScale(d.date) - 5}
y={0}
width={10}
height={innerHeight}
fill="transparent"
onMouseMove={(event) => {
const coords = localPoint(event)
showTooltip({ tooltipData: d, tooltipLeft: coords?.x, tooltipTop: coords?.y })
}}
onMouseLeave={hideTooltip}
/>
))}
<AxisBottom top={innerHeight} scale={xScale} tickFormat={(d) => d.toLocaleDateString()} />
<AxisLeft scale={yScale} />
</Group>
{tooltipData && (
<TooltipWithBounds top={tooltipTop} left={tooltipLeft}>
{tooltipData.value.toLocaleString()}/wk
</TooltipWithBounds>
)}
</svg>
)
}
Visx requires significantly more code but every pixel is intentional.
Feature Comparison
| Feature | Recharts | Chart.js | Nivo | Visx |
|---|---|---|---|---|
| React-native API | Yes (JSX) | Config-based | Yes (Props) | Yes (Components) |
| TypeScript | Yes | Yes | Yes | Yes |
| Rendering | SVG | Canvas | SVG + Canvas | SVG |
| Large datasets (10K+) | Slow | Yes (Canvas) | Yes (Canvas mode) | Yes (with optimization) |
| Customization level | Medium | Medium | Medium-High | Maximum |
| Chart types | 10+ | 30+ | 30+ | Unlimited (primitives) |
| Default aesthetics | Good | Good | Excellent | DIY |
| Accessibility | Partial | Limited | Excellent | Manual |
| Animation | Yes | Yes | Spring-based | D3 transitions |
| Bundle size | ~370KB | ~65KB | Modular | Modular |
| Learning curve | Low | Low | Medium | High |
Ecosystem and Community
Recharts has a large, active GitHub community with over 23,000 stars. The issues tracker is well-maintained, and the v3 release brought better TypeScript support and React 18 compatibility. The documentation is solid, with CodeSandbox examples for each chart type. The maintainer community is responsive to bugs — most critical issues get addressed within days. It's compatible with shadcn/ui charts, which uses Recharts as its underlying rendering engine, driving significant additional adoption.
Chart.js's community is the largest of the four by a significant margin. With over 64,000 GitHub stars and 4M weekly downloads, it has the broadest ecosystem of tutorials, Stack Overflow answers, and community plugins. The chartjs-plugin-zoom, chartjs-plugin-annotation, and chartjs-chart-financial extensions are maintained by the Chart.js organization. For teams where the person writing the chart code might not be a dedicated frontend developer, Chart.js's configuration-based API is often easier to reason about than JSX.
Nivo's niche is design-conscious teams that want beautiful defaults without custom CSS work. The nivo.rocks interactive playground lets you configure any chart visually and copy the props. Nivo's accessibility-first approach — all SVG charts include ARIA labels and keyboard navigation — makes it the only choice from this list that meets WCAG 2.1 AA out of the box without additional work.
Visx is actively maintained by Airbnb and has a focused community of data visualization engineers. Because it's D3 primitives wrapped in React, the D3 community's knowledge transfers directly. The tradeoff is a significantly steeper learning curve — teams without D3 experience typically need two to three times as long to build their first chart compared to Recharts.
Real-World Adoption
Recharts is the default choice in the shadcn/ui ecosystem. The shadcn/ui charts module generates Recharts-based chart components with your design system tokens applied. This has driven adoption in a large number of Next.js admin dashboards and SaaS products that already use shadcn/ui. Companies shipping internal analytics dashboards — where standard bar, line, area, and pie charts cover 90% of use cases — almost universally choose Recharts in 2026.
Chart.js continues to dominate in non-React contexts (Vue, Angular, server-side rendering, vanilla JS). For React specifically, react-chartjs-2 wraps Chart.js cleanly, and teams that already know Chart.js from previous projects reach for it by default. The financial charting plugin makes it the standard for stock charts, candlestick charts, and OHLC visualizations in JavaScript.
Nivo sees adoption in products where design quality and accessibility are first-class requirements. Government data dashboards, journalism data visualizations, and healthcare analytics tools — contexts where WCAG compliance matters — frequently use Nivo for its out-of-the-box accessibility features. The Sankey, Chord, and Waffle chart types cover specialized visualization needs that neither Recharts nor Chart.js handles well.
Visx powers the most complex data visualization products in JavaScript: custom trading dashboards, scientific data visualization, and products that need highly interactive custom charts beyond what any standard library provides. Airbnb uses it internally for their analytics products, and the pattern of composing D3 primitives through React components rather than using D3 directly has become a preferred approach at companies with strong data engineering cultures.
Performance and Benchmarks
For datasets under 1,000 points, all four libraries are fast enough that performance isn't a differentiating factor — frame rates are 60fps and interaction latency is imperceptible. At 10,000+ data points, the rendering approach starts to matter.
Chart.js's Canvas rendering is the fastest for large datasets. It renders 100,000 points in a line chart at smooth framerates because Canvas compositing is handled by the GPU. SVG-based libraries (Recharts, Visx) degrade at scale — the DOM grows proportionally with the dataset, and style recalculations become expensive.
Nivo addresses this with its Canvas variants — ResponsiveLineCanvas uses the same props as ResponsiveLine but renders via Canvas for large datasets. This gives you Nivo's clean API and design quality without the SVG performance ceiling.
For Recharts, the largeData prop and virtualization patterns help with very large lists, but for genuine time-series data with thousands of points, Chart.js or a Canvas-mode Nivo chart is the better choice.
Getting Started: Migration to Recharts
For teams migrating from Chart.js to Recharts in a React codebase, the mental model shift is from configuration objects to JSX composition. A Chart.js line chart with custom tooltip becomes a <LineChart> with nested <Line>, <Tooltip>, and <XAxis> components. The one-time investment in learning the component model pays off as charts become easier to customize — changing colors, adding data series, or swapping chart types is editing JSX rather than updating deeply nested config objects.
The migration path for large Chart.js codebases typically proceeds chart by chart, starting with the simplest visualizations. Teams often run both libraries in parallel during transition, using Recharts for new charts and Chart.js for charts that already work.
When to Use Each
Choose Recharts if:
- Building standard dashboards (line, bar, area, pie)
- You want JSX-first React integration without config objects
- Developer velocity matters more than pixel-perfect customization
- You use shadcn/ui and want consistent design system integration
Choose Chart.js (react-chartjs-2) if:
- You have an existing Chart.js codebase
- You need canvas performance for thousands of data points
- You need specialized chart types (bubble, financial, polar)
Choose Nivo if:
- You want beautiful, accessible charts out of the box
- You need specialized chart types (sankey, heatmap, waffle, chord)
- Accessibility and screen reader support are priorities
- WCAG compliance is required without additional customization
Choose Visx if:
- You need fully custom data visualizations that no library covers
- Your team knows D3 and wants React integration without abstraction
- Building complex interactive charts (brush selection, custom interactions)
Methodology
Download data from npm registry (weekly average, February 2026). Bundle sizes from bundlephobia. Chart type counts from official documentation. Learning curve assessment based on community feedback and API complexity.
TypeScript Integration Depth
TypeScript support across these four libraries varies in ways that matter for large codebases. Recharts v2.5+ ships TypeScript types directly in the package, and the component props are well-typed. However, some advanced customization patterns — custom tick renderers, custom active shape renderers for Pie charts — require casting to any in a few places because the callback type signatures don't always match what TypeScript infers. For most dashboards using standard configuration, Recharts TypeScript types are entirely adequate.
Chart.js has bundled TypeScript declarations since v3. The ChartOptions<ChartType> generics system is powerful — it lets you specify the chart type and get appropriately typed options. react-chartjs-2 wraps this with ChartProps<ChartType> that preserves the typing. Complex dataset configurations for multi-type charts (charts with both bar and line datasets) sometimes require explicit type assertions because Chart.js's configuration structure has some inherent ambiguity.
Nivo's TypeScript types are thorough. Every prop for every chart component is typed, the data format types are clearly documented, and the theme system is strongly typed. Nivo's modular structure means that @nivo/line, @nivo/bar, and other packages each export their specific types, keeping the type definitions focused and readable.
Visx's TypeScript integration is the strongest of the four, largely because the library was built TypeScript-first. The D3 scale types (ScaleLinear<number, number>, ScaleTime<number, number>) flow through the component hierarchy correctly. The tooltip system with useTooltip<DatumType> preserves the datum type through the tooltip callback, making custom tooltips fully typed. This is particularly valuable for complex visualizations where the tooltip needs to access multiple fields of a data object.
Testing Charts in React Applications
Testing chart components presents unique challenges: charts rely on SVG rendering, Canvas APIs, and sometimes dynamic sizing that doesn't work in jsdom. Each library has established patterns for handling this.
Recharts is the easiest to test in React Testing Library. The SVG output is queryable with querySelector and role-based selectors. The ResponsiveContainer component relies on ResizeObserver, which requires a polyfill in Jest (resize-observer-polyfill). A common testing pattern is to mock ResponsiveContainer to return fixed dimensions, making chart rendering deterministic. Data-driven assertions — checking that a <Line> with the correct data points exists — are straightforward.
Chart.js requires a Canvas mock (jest-canvas-mock) because it renders to a <canvas> element that jsdom doesn't support. Testing Chart.js charts typically means testing the configuration passed to Chart.js rather than the rendered output — verifying that a dataset has the correct data array and color configuration, not that specific pixels are drawn. This is a reasonable testing strategy for configuration-driven libraries.
Nivo and Visx charts are SVG-based and generally testable in jsdom without special mocking. The SVG DOM is queryable, and accessibility attributes on Nivo charts make them particularly testable with getByRole and getByLabelText queries.
The charting library decision also connects to your state management and data fetching approach. Charts that respond to real-time data — websocket streams, polling endpoints — benefit from libraries with efficient re-rendering on data updates. Recharts and Nivo both use React's reconciliation, meaning component re-renders trigger SVG updates. For high-frequency data updates (more than a few times per second), Chart.js's Canvas-based rendering has a performance advantage because it bypasses React's reconciler entirely.
Teams building analytics dashboards should also consider their component library. Recharts integrates particularly well with shadcn/ui's design tokens and Tailwind's color palette because Recharts accepts standard React props for styling. Nivo's theming system is more self-contained but equally capable. The best React component libraries 2026 comparison covers the broader component ecosystem these charting libraries integrate with.
For teams testing data visualization components, best JavaScript testing frameworks 2026 covers how Vitest and Jest handle SVG and Canvas rendering in testing environments.
The Best JavaScript Charting Libraries 2026 comparison covers the broader landscape beyond React-specific libraries, including vanilla JS options and chart builders that don't require a framework.