TL;DR: Recharts is a composable chart library for React built on D3. Combine
<LineChart>,<Line>,<XAxis>,<Tooltip>as JSX. Wrap in<ResponsiveContainer>to scale with parent width. ~428kB uncompressed (~120kB gzipped). Official docs: recharts.org.
What is Recharts?
Recharts is a composable charting library that wraps D3 in React components. You build charts by composing primitives: <LineChart> + <Line> + <XAxis> + <Tooltip> + <Legend>. This declarative API makes it easy to add, remove, or customise chart elements without touching D3 internals.
When to use it
Use Recharts for standard business charts (line, bar, area, pie) in React apps when you don't need to write D3 directly.
Recharts vs Chart.js: Recharts is the natural choice for React apps - it's component-based and re-renders correctly. Chart.js uses canvas and requires manual cleanup. For complex or highly custom visualisations, consider Visx (D3 + React hooks, lower abstraction).
Note on bundle size: At ~428kB uncompressed (~120kB gzipped), Recharts is heavier than alternatives like Tremor or Victory. Consider lazy-loading chart components if they're not on the critical path.
How the composition model pays off
The reason Recharts feels natural to React developers is that a chart is just a tree of components. You don't call an imperative API and pass a giant config object. You write JSX, and each piece of the chart maps to a tag you can see. Want a grid? Add <CartesianGrid>. Need a second metric? Drop in another <Line> with a different dataKey. Remove a feature by deleting its element.
That model makes charts easy to read six months later, which is more valuable than it sounds. A config-object chart library tends to produce a wall of nested options where it's hard to tell what's actually rendered. With Recharts, a teammate can skim the JSX and know exactly what shows up on screen. It also means conditional rendering works the way you'd expect: wrap a <ReferenceLine> in a {showTarget && ...} and it appears or disappears with normal React logic.
The flip side is that deeply custom visualizations can fight the abstraction. If you need an unusual axis, a non-standard shape, or pixel-perfect control over every element, you'll spend time working around Recharts rather than with it. At that point a lower-level tool like Visx, which gives you D3 scales plus React rendering, is the better fit.
Making charts responsive and performant
ResponsiveContainer is the piece that makes charts adapt to their parent. It watches the parent's size and feeds the right width and height to the chart. The single most common Recharts bug is a chart that renders at zero height, and it's almost always because the parent has no defined height for the container to read. Give the parent an explicit height, or set a fixed height on the container itself, and the problem disappears.
Performance is mostly fine until two situations: large datasets and frequent updates. Recharts renders SVG, so every data point is a DOM node. A line with a few hundred points is fine; one with tens of thousands will get sluggish because the browser is managing that many nodes. For dense series, downsample your data before passing it in, or reach for a canvas-based library that doesn't pay the per-node cost.
The other one is animation on live data. Recharts animates transitions by default, which looks great on first load and terrible on a dashboard that refreshes every second, because each update restarts the animation and the chart visibly stutters. Set isAnimationActive={false} on your <Line>, <Bar>, or <Area> for any chart fed by polling or websockets. Keep the animation for static reports where it adds polish.
Customizing tooltips, axes, and annotations
Almost every real chart needs formatting the defaults don't provide. Axis ticks are the usual first stop. Use tickFormatter on <XAxis> or <YAxis> to turn raw numbers into currency, percentages, or compact notation, and to format timestamps into readable dates. This keeps the underlying data numeric, which matters because Recharts needs real numbers to compute scales correctly.
Tooltips are where you can add the most value with the least effort. The default tooltip is functional but plain. Passing your own component to <Tooltip content={...} /> gives you full JSX control, so you can show formatted values, add context, highlight the most important series, or match your brand styling exactly. Because it's a normal component, you can reuse the same number formatters you use elsewhere in the app.
For thresholds and targets, <ReferenceLine> and <ReferenceArea> let you annotate directly on the chart. A dashed reference line at a sales goal, or a shaded band marking an acceptable range, communicates far more than the raw series alone. These small additions are what separate a chart that merely plots data from one that tells the reader what to think about it.
Key Features
- Composable - build any chart by combining axis, grid, tooltip, and data components
- Responsive -
<ResponsiveContainer>scales to parent dimensions - Custom tooltips - full JSX control over tooltip rendering
- Reference lines/areas - annotate charts with threshold lines
- Brush component - interactive timeline scrubber for zooming
Installation
npm install recharts
Basic Usage
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer
} from 'recharts'
const data = [
{ month: 'Jan', users: 400, revenue: 2400 },
{ month: 'Feb', users: 620, revenue: 3800 },
{ month: 'Mar', users: 580, revenue: 3200 },
{ month: 'Apr', users: 890, revenue: 4800 },
]
function UsersChart() {
return (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="users" stroke="#6366f1" strokeWidth={2} dot={false} />
<Line type="monotone" dataKey="revenue" stroke="#10b981" strokeWidth={2} dot={false} />
</LineChart>
</ResponsiveContainer>
)
}
Custom Tooltip
interface TooltipProps {
active?: boolean
payload?: Array<{ name: string; value: number; color: string }>
label?: string
}
function CustomTooltip({ active, payload, label }: TooltipProps) {
if (!active || !payload?.length) return null
return (
<div className="bg-white border border-slate-200 rounded-lg p-3 shadow-lg text-sm">
<p className="font-semibold text-slate-800 mb-1">{label}</p>
{payload.map(entry => (
<p key={entry.name} style={{ color: entry.color }}>
{entry.name}: {entry.value.toLocaleString()}
</p>
))}
</div>
)
}
// Usage:
<Tooltip content={<CustomTooltip />} />
TypeScript Tips
Type your data array to catch mismatched dataKey values early:
interface ChartDataPoint {
month: string
users: number
revenue: number
}
const data: ChartDataPoint[] = [...]
// dataKey must match a key of ChartDataPoint
<Line dataKey="users" /> // matches a known key
<Line dataKey="sessions" /> // TypeScript won't catch this - Recharts uses string keys
Common Gotchas
ResponsiveContainerneeds a defined parent height - if the parent has no height, the chart renders at 0px. SetheightonResponsiveContaineror ensure the parent has explicit height- Custom tick formatting - use
tickFormatteronXAxis/YAxisfor number/date formatting - Animation on data updates - Recharts animates by default. Set
isAnimationActive={false}on chart elements for real-time data that updates frequently
Related
- TanStack Query - fetch and cache the data you pass to Recharts; handles loading, error, and refetch states
- TanStack Table - pair a table with a chart for full data exploration: table for details, Recharts for overview