Recharts vs Chart.js vs Nivo vs Visx: React Charting in 2026
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 | ✅ JSX | ⚠️ Config | ✅ Props | ✅ Components |
| TypeScript | ✅ | ✅ | ✅ | ✅ |
| Rendering | SVG | Canvas | SVG + Canvas | SVG |
| Large datasets (10K+) | ⚠️ Slow | ✅ Canvas | ✅ Canvas mode | ✅ With optimization |
| Customization level | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Chart types | 10+ | 30+ | 30+ | Unlimited (primitives) |
| Default aesthetics | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | DIY |
| Accessibility | ⚠️ Partial | ⚠️ Limited | ✅ Excellent | ✅ (manual) |
| Animation | ✅ | ✅ | ✅ Spring-based | ✅ D3 transitions |
| Bundle size | ~370KB | ~65KB | Modular | Modular |
| Learning curve | Low | Low | Medium | High |
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
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
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.