Data visualization in React is one of those things that seems simple until you actually try to build it. A basic bar chart? Easy. Now add tooltips that show multiple data points, responsive sizing that doesn't break on mobile, accessibility labels for screen readers, animations that don't drop frames, and the ability to customize every color and font to match your brand. Suddenly you're knee-deep in D3 code wondering if there's a better way.
There is. The React ecosystem has several mature charting libraries that abstract away the complexity of SVG and canvas rendering while giving you enough control to make the output look exactly how you want. The challenge is picking the right one. They differ significantly in philosophy, bundle size, TypeScript support, and the level of control they offer.
I've used most of them in production at various points, and the differences that matter day-to-day aren't always obvious from the documentation landing pages. Here's a practical comparison of the major players, what they're good at, and where they'll frustrate you.
Recharts is the most popular React charting library by a comfortable margin. If you've seen a chart in a React application, there's a good chance it was built with Recharts. The API is declarative and component-based, which feels natural in React.
import {
LineChart,
Line,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
const data = [
{ month: "Jan", revenue: 4000 },
{ month: "Feb", revenue: 3000 },
{ month: "Mar", revenue: 5000 },
];
function RevenueChart() {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="revenue" stroke="#3b82f6" />
</LineChart>
</ResponsiveContainer>
);
}
The composable component model is Recharts' biggest strength. You build charts by nesting components—an <XAxis> inside a <LineChart> inside a <ResponsiveContainer>. The structure mirrors the visual hierarchy of the chart, which makes the code easy to read and modify.
Customization is straightforward for common needs. Colors, stroke widths, axis formatting, and tooltip content are all exposed as props. The documentation is solid, and there are enough examples online that you'll rarely be the first person to encounter a particular problem.
The tradeoff is that Recharts can feel rigid when you need something truly custom. The library provides a set of chart types—line, bar, pie, area, scatter, radar—and if your design fits within those, you're golden. If you need a chart that doesn't map neatly to one of those types, you'll find yourself fighting the abstraction rather than working with it.
Performance is generally good for charts with hundreds of data points. Recharts uses SVG, which means each data point is a DOM element. At a few hundred points, that's fine. At a few thousand, you'll start seeing render bottlenecks. For large datasets, a canvas-based library would be better, but for dashboards and analytics interfaces, Recharts handles typical data volumes without issue.
Victory comes from Formidable, the same team behind Spectacle and other well-regarded open-source projects. If Recharts is the pragmatic default, Victory is the option for teams that know they'll need deep customization.
Victory's API is also declarative, but it exposes more configuration points than Recharts. Animations, event handling, container queries for responsiveness, and axis labeling are all highly configurable.
import {
VictoryChart,
VictoryLine,
VictoryAxis,
VictoryTooltip,
VictoryVoronoiContainer,
} from "victory";
function RevenueChart() {
return (
<VictoryChart containerComponent={<VictoryVoronoiContainer />}>
<VictoryAxis tickFormat={(t) => `${t}`} />
<VictoryAxis dependentAxis />
<VictoryLine
data={[
{ x: "Jan", y: 4000 },
{ x: "Feb", y: 3000 },
{ x: "Mar", y: 5000 },
]}
style={{ data: { stroke: "#3b82f6" } }}
labels={({ datum }) => `$${datum.y}`}
labelComponent={<VictoryTooltip />}
/>
</VictoryChart>
);
}
The VictoryVoronoiContainer is a standout feature—it creates an invisible Voronoi diagram over the chart, making it easy to hover near a data point and get a tooltip. This solves the frustration of trying to precisely mouse over a thin line.
Victory's biggest downside is bundle size. It's meaningfully larger than Recharts because it includes more features out of the box. For a dashboard with a single chart, that might not matter. For a marketing site where every kilobyte counts, it's worth measuring.
TypeScript support is decent but not flawless. The library has type definitions, but some of the more advanced customization—custom label components, complex event handlers—can produce type errors that require casting. It's not a dealbreaker, but it's less polished than some of the newer libraries.
If you've been working on preventing unnecessary re-renders in React, you'll appreciate that Victory gives you fine-grained control over when chart components update. The animate prop and event system let you optimize performance without tearing down and rebuilding the entire chart on every data change.
Nivo is built on top of D3, but you'd never know it from the API. It wraps D3's powerful layout algorithms in a React-friendly component model that produces stunning charts with minimal configuration.
import { ResponsiveLine } from "@nivo/line";
function RevenueChart() {
const data = [
{
id: "revenue",
data: [
{ x: "Jan", y: 4000 },
{ x: "Feb", y: 3000 },
{ x: "Mar", y: 5000 },
],
},
];
return (
<div style={{ height: 300 }}>
<ResponsiveLine
data={data}
margin={{ top: 20, right: 20, bottom: 50, left: 50 }}
colors={["#3b82f6"]}
enablePoints={false}
useMesh={true}
axisBottom={{ legend: "Month" }}
axisLeft={{ legend: "Revenue" }}
/>
</div>
);
}
Nivo is opinionated about aesthetics. The default color palettes, typography, and spacing look polished without any customization. If you want a chart that looks great with minimal effort, Nivo delivers faster than any other library in this comparison.
The useMesh prop enables a hover detection layer similar to Victory's Voronoi container, making tooltips easy to trigger. Interactive features like legends, crosshairs, and slice highlighting are built in and work consistently.
Server-side rendering is a first-class feature. Nivo charts render to SVG, which means they work with Next.js server components without hydration mismatches. If you're building with React Server Components, Nivo integrates more smoothly than canvas-based alternatives.
The tradeoff is that Nivo's customization depth works differently than Recharts or Victory. You configure the chart through props, and those props map to D3's internal settings. If a prop doesn't expose what you need, you're stuck. Nivo doesn't give you access to the underlying D3 objects, so truly custom behavior requires forking or looking elsewhere.
visx takes a fundamentally different approach. Instead of providing chart components, it provides low-level visualization primitives that you compose yourself. Think of it as D3 with a React-friendly wrapper—you get the power of D3's math and layout functions, but you render with React components instead of D3's enter/update/exit pattern.
import { LinePath } from "@visx/shape";
import { scaleTime, scaleLinear } from "@visx/scale";
import { AxisLeft, AxisBottom } from "@visx/axis";
import { Grid } from "@visx/grid";
function RevenueChart({ data, width, height }) {
const xScale = scaleTime({
domain: [data[0].date, data[data.length - 1].date],
range: [0, width],
});
const yScale = scaleLinear({
domain: [0, Math.max(...data.map((d) => d.value))],
range: [height, 0],
});
return (
<svg width={width} height={height}>
<Grid xScale={xScale} yScale={yScale} width={width} height={height} />
<AxisBottom scale={xScale} top={height} />
<AxisLeft scale={yScale} />
<LinePath
data={data}
x={(d) => xScale(d.date)}
y={(d) => yScale(d.value)}
stroke="#3b82f6"
/>
</svg>
);
}
visx is not a charting library. It's a set of building blocks for making your own charting library. You manage the scales, the axes, the layout, and the rendering. The benefit is unlimited flexibility. If you can describe the visualization mathematically, you can build it with visx.
The cost is that you're doing more work. There's no <LineChart> component that handles axes and tooltips for you. You build that abstraction yourself, which means more code and more decisions. visx is the right choice when the other libraries don't give you enough control, or when your team has the D3 expertise to build custom visualizations from primitives.
Bundle size is a strong point. Because visx is split into dozens of small packages, you only import what you use. No chart types you don't need. No features you won't touch. The tree-shaking is excellent.
If you're styling your charts and want them to match your application's design system, visx gives you full control. Unlike libraries that abstract the rendering into opaque components, visx exposes the SVG elements directly. You can style them however you want—with CSS Modules, Tailwind, or inline styles. If you've been comparing CSS Modules vs Tailwind, visx works equally well with either approach.
react-chartjs-2 wraps Chart.js, one of the most popular charting libraries in any ecosystem. Chart.js is mature, well-documented, and handles a wide variety of chart types. The React wrapper makes it work idiomatically with React's rendering model.
import { Line } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Tooltip,
} from "chart.js";
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Tooltip,
);
function RevenueChart() {
const data = {
labels: ["Jan", "Feb", "Mar"],
datasets: [
{ label: "Revenue", data: [4000, 3000, 5000], borderColor: "#3b82f6" },
],
};
return <Line data={data} />;
}
The biggest advantage of react-chartjs-2 is Chart.js itself. The underlying library supports canvas rendering, which makes it dramatically faster than SVG-based libraries for large datasets. If you're plotting thousands of points, Chart.js will outperform Recharts, Victory, and Nivo by a wide margin.
Canvas rendering also means the chart is a single <canvas> element rather than hundreds of SVG nodes. This reduces DOM size and improves rendering performance, especially on pages with multiple charts.
The tradeoff is customization. Canvas charts are harder to style with CSS because you're not dealing with DOM elements. Chart.js provides extensive configuration options, but they're JavaScript objects, not CSS rules. If your design team wants pixel-perfect control over every visual element, a canvas-based library creates friction.
Accessibility requires extra attention. SVG charts can include <title>, <desc>, and ARIA attributes naturally. Canvas charts need fallback content and keyboard navigation implemented manually. If you're building public-facing dashboards, SVG-based libraries make accessibility easier. For internal analytics tools where performance matters more, canvas is often the right tradeoff.
A few years back, I built an analytics dashboard for a fintech startup. The requirements were ambitious: real-time updating charts, complex financial visualizations with dual axes and custom tooltips, and the ability to export charts as PNG images for reports. The target audience was financial analysts who lived in these dashboards all day.
I picked Recharts because it was the library I knew best. The initial build went fast—line charts, bar charts, pie charts, all the standards. Then the custom requests started rolling in. A candlestick chart for stock data. A heatmap for trading volume by hour. A chart that overlaid three different data series on two different Y-axes with synchronized crosshairs.
Recharts couldn't do the candlestick chart natively. The dual-axis chart was possible but required so much customization that the abstraction stopped being helpful. The heatmap needed a completely different library. By the end, the dashboard was importing three different charting libraries, each with its own bundle weight and its own slightly different visual style.
If I were starting that project today, I'd use visx. The flexibility to build exactly the visualizations the product needed, without fighting someone else's abstraction, would have been worth the additional upfront work. But at the time, the sunk cost of the existing Recharts code made a full rewrite impractical.
The lesson: think about the most complex chart you'll need, not the simplest one. Pick a library that can handle the ceiling, not just the floor.
Bundle size matters, especially on marketing sites or mobile-first applications. Here's how the libraries compare when you add a basic line chart to your project (gzipped, approximate):
These numbers are ballpark figures. Tree-shaking, version differences, and which chart types you import will change the exact size. But the pattern is clear: visx is the smallest because you only import what you use. Victory and Chart.js are the largest because they include more features by default.
For most applications, the difference between 43 KB and 60 KB is negligible compared to the rest of your JavaScript bundle. But if you're building a landing page with a single chart, visx's modular approach keeps the bundle lean.
TypeScript support has become a baseline expectation for React libraries. All five libraries have type definitions, but the quality varies.
Recharts has improved its types significantly in recent versions. The component props are well-typed, and common patterns like custom tooltips work without type assertions. There are still gaps in the more advanced customization paths, but for standard usage, TypeScript integration is smooth.
Nivo's types are generated from its source code, which means they're comprehensive. The downside is that error messages can be cryptic—a misconfigured prop produces a type error that references Nivo's internal type definitions rather than the prop you actually set.
visx's types are excellent because the library is written in TypeScript. The scale functions, shape components, and axis components are all typed precisely. If you're comfortable with TypeScript generics, visx's type system is a joy to work with.
If TypeScript error handling matters to you—and if you've read our guide on TypeScript error handling in try catch blocks, it probably does—you'll appreciate libraries that surface type errors clearly rather than burying them in generic type resolution failures.
Charts are data, and data should be accessible to everyone. Screen reader users, keyboard-only users, people with color vision deficiencies—they all need to access the information your charts convey. The charting library you choose affects how easy that is.
Recharts renders SVG elements, which means you can add <title> and <desc> tags, apply ARIA roles, and make individual data points focusable. The library doesn't handle this automatically, but SVG makes it possible to implement without fighting the rendering layer.
Nivo's server-side rendering support means charts can deliver meaningful markup to screen readers even before JavaScript loads. The library doesn't include built-in accessibility features, but the SVG output is accessible to assistive technology with proper configuration.
visx gives you the most control. Because you're building the SVG structure yourself, you can add semantic elements, ARIA attributes, and keyboard handlers precisely where they're needed. It's more work, but the result can be genuinely accessible in a way that higher-level abstractions often prevent.
If you've been implementing accessible components with React Aria Components, you'll find that charting accessibility is still a frontier. Most charting libraries treat it as an afterthought. visx is your best bet if accessibility is a hard requirement, simply because it gives you the control to build what you need.
The right library depends on what you're building and who you're building it for.
Pick Recharts if you need standard chart types, want to move fast, and value a large community with extensive examples. It's the safe choice for dashboards, analytics, and internal tools where the chart types fit within the library's wheelhouse.
Pick Victory if you need deep customization within a declarative API, especially for interactive charts with tooltips, brushing, and zooming. The Voronoi container alone justifies the choice for dense data visualizations.
Pick Nivo if you want beautiful charts with minimal configuration and need server-side rendering support. The opinionated design produces polished output faster than any other library.
Pick visx if you need unlimited flexibility, have D3 experience on your team, or are building a custom design system where charts must match a specific visual language. The bundle size and TypeScript support are best in class.
Pick react-chartjs-2 if you're plotting large datasets and need canvas-based rendering for performance, or if your team already knows Chart.js from non-React projects.
Need data visualizations that perform well and look great? Red Surge Technology builds React dashboards and data-driven interfaces that handle real-world complexity. Get in touch to discuss your project.