Customize
A guide showing various props and Recharts customization variants.
This guide shows customization on individual chart level. If you want to apply the same style to multiple charts, check out the theme guide.
The two compose in one direction: an explicit prop always wins over the theme. Reach for the theme when you are describing how your charts should look in general, and for props when you are describing this particular chart.
Every visual detail is reachable. Presentation attributes are plain props, text goes through formatters, and anywhere Recharts draws something for you there is usually a shape, content, or tick prop that lets you draw it yourself instead.
Presentation props
Recharts draws SVG, and SVG presentation attributes are just props: fill, stroke, strokeWidth, strokeDasharray, fillOpacity, className, style, and the rest. They pass through to the underlying element, so anything you can do in SVG you can do from JSX.
Tooltip and Legend are the exceptions: they render HTML, so they take CSS style objects rather than SVG attributes. Everything else in the chart is SVG.
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
const data = [
{ month: 'Jan', revenue: 4200 },
{ month: 'Feb', revenue: 5800 },
{ month: 'Mar', revenue: 7200 },
{ month: 'Apr', revenue: 6100 },
{ month: 'May', revenue: 8900 },
{ month: 'Jun', revenue: 7400 },
];
export default function CustomizeSizeAndStroke() {
return (
<BarChart
style={{ width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
>
{/* Every SVG presentation attribute is available as a prop. */}
<CartesianGrid stroke="#94a3b8" strokeDasharray="5 5" strokeOpacity={0.5} />
<XAxis dataKey="month" stroke="#e11d48" />
<YAxis stroke="#e11d48" strokeWidth={2} />
{/* Tooltip is HTML, so it takes `style` and `className` rather than SVG attributes. */}
<Tooltip defaultIndex={2} />
<Bar
dataKey="revenue"
fill="#0ea5e9"
fillOpacity={0.85}
stroke="#0369a1"
strokeWidth={2}
radius={4}
barSize={30}
/>
<RechartsDevtools />
</BarChart>
);
}If you find yourself passing the same colors to every chart, that is the signal to move them into a theme instead.
Labels and ticks
Text in a chart comes from three different places, and each has its own escape hatch:
- Axis titles are the
label prop on XAxis and YAxis. Pass a string, a props object with a position, or your own element. - Tick text goes through
tickFormatter. For full control over how a tick is drawn - rotation, multiple lines, icons - pass a component to the tick prop. The axis ticks guide covers this in depth. - Data labels come from LabelList (or the
label prop on a graphical item, which is shorthand for the same thing). Use formatter to change the text and content to change the rendering.
import { Bar, BarChart, LabelList, LabelProps, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
const data = [
{ month: 'Jan', revenue: 4200, profit: 1100 },
{ month: 'Feb', revenue: 5800, profit: 1500 },
{ month: 'Mar', revenue: 7200, profit: 2400 },
{ month: 'Apr', revenue: 6100, profit: 1800 },
{ month: 'May', revenue: 8900, profit: 3100 },
{ month: 'Jun', revenue: 7400, profit: 2600 },
];
const formatMonth = (value: string): string => value.toUpperCase();
const formatThousands = (value: number): string => `${value / 1000}k`;
/**
* A `content` render function gets the resolved geometry of the label,
* so you can place and shape it however you like.
*/
function ProfitLabel({ x, y, width, value }: LabelProps) {
if (x == null || y == null || width == null) {
return null;
}
return (
<text x={Number(x) + Number(width) / 2} y={Number(y)} dy={-6} textAnchor="middle" fontSize={11}>
▲ {value}
</text>
);
}
export default function CustomizeLabels() {
return (
<BarChart
style={{ width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
margin={{ top: 25, right: 30, left: 20, bottom: 25 }}
>
{/* Axis titles are the `label` prop; tick text goes through `tickFormatter`. */}
<XAxis
dataKey="month"
tickFormatter={formatMonth}
label={{ position: 'insideBottomRight', value: 'Month', offset: -10 }}
/>
<YAxis
tickFormatter={formatThousands}
label={{ position: 'insideTopLeft', value: 'Revenue', angle: -90, dy: 60 }}
/>
{/* LabelList with a formatter covers the common case ... */}
<Bar dataKey="revenue">
<LabelList dataKey="revenue" position="top" formatter={label => `${Number(label) / 1000}k`} fontSize={11} />
</Bar>
{/* ... and a `content` component covers everything else. */}
<Bar dataKey="profit">
<LabelList dataKey="profit" content={ProfitLabel} />
</Bar>
<RechartsDevtools />
</BarChart>
);
}Shapes
The shape prop replaces how a graphical item draws itself. It accepts a component, or a plain props object that gets merged into the default shape. It is available on Area, Bar, Line, Scatter, Pie, Radar, RadialBar and Funnel, as well as on ReferenceArea, ReferenceDot and ReferenceLine.
Each graphical item also has an active variant, which applies only while the tooltip is pointing at that item. It takes the same forms, so a highlight can be as small as { fillOpacity: 0.4 }. The prop name differs by component:
To keep things simple you can also read the isActive prop inside the shape. Set activeBar= on the Bar component so that React creates event handlers in a BarChart.
import { Bar, BarChart, BarShapeProps, Tooltip, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
const data = [
{ month: 'Jan', revenue: 4200 },
{ month: 'Feb', revenue: 5800 },
{ month: 'Mar', revenue: 7200 },
{ month: 'Apr', revenue: 6100 },
{ month: 'May', revenue: 8900 },
{ month: 'Jun', revenue: 7400 },
];
const getPath = (x: number, y: number, width: number, height: number) =>
`M${x},${y + height}
C${x + width / 3},${y + height} ${x + width / 2},${y + height / 3} ${x + width / 2}, ${y}
C${x + width / 2},${y + height / 3} ${x + (2 * width) / 3},${y + height} ${x + width}, ${y + height}
Z`;
/**
* A shape component receives the resolved geometry and the resolved styles -
* including whatever the theme supplied - and returns any SVG you like.
*
* `isActive` is true if this shape is currently highlighted by a mouse cursor or keyboard shortcut.
*/
export function TriangleBar(props: BarShapeProps) {
const { fill, fillOpacity, x, y, width, height, isActive } = props;
if (x == null || y == null || width == null || height == null) {
return null;
}
return (
<path
d={getPath(Number(x), Number(y), Number(width), Number(height))}
stroke="none"
fill={fill}
fillOpacity={isActive ? 0.4 : fillOpacity}
/>
);
}
export default function CustomizeBarShape() {
return (
<BarChart
style={{ width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
>
<XAxis dataKey="month" />
<YAxis />
<Tooltip defaultIndex={4} />
{/*
* `shape` replaces the normal rendering.
*
* Individual bars by default do not respond to mouse events (a performance optimization for large charts).
* pass `activeBar` (a shortcut for activeBar={true}) to receive `isActive` prop inside the `shape` component.
*/}
<Bar dataKey="revenue" shape={TriangleBar} activeBar />
<RechartsDevtools />
</BarChart>
);
}A shape component receives the resolved geometry and the resolved styles, theme included, so it can honour the surrounding theme or ignore it. If you only want rounded corners, Bar has a radius prop and no custom shape is needed - see the rounded bars guide.
Tooltip and Legend content
The content prop on Tooltip and Legend hands you the whole thing: the active payload, the label, and whether it is currently visible. Return any React you like. The payload carries your original data point, so anything in your data - not just the plotted value - is available.
import { Bar, BarChart, Tooltip, TooltipContentProps, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
const data = [
{ month: 'Jan', revenue: 4200, note: 'New year promo' },
{ month: 'Feb', revenue: 5800, note: 'Referral campaign' },
{ month: 'Mar', revenue: 7200, note: 'Spring launch' },
{ month: 'Apr', revenue: 6100, note: 'Steady state' },
{ month: 'May', revenue: 8900, note: 'Conference season' },
{ month: 'Jun', revenue: 7400, note: 'Summer slowdown' },
];
/**
* `content` hands you the whole tooltip: the active payload, the label and
* whether the tooltip is currently visible. Return any React - it is plain HTML,
* not SVG, so normal CSS applies.
*/
function RevenueTooltip({ active, payload, label }: TooltipContentProps) {
if (!active || payload == null || payload.length === 0) {
return null;
}
const entry = payload[0];
const point = entry?.payload;
return (
<div
style={{
border: '1px solid #d88488',
backgroundColor: '#fff',
color: '#18181b',
padding: 10,
borderRadius: 5,
boxShadow: '1px 1px 2px #d88488',
}}
>
<p style={{ margin: 0, fontWeight: 700 }}>
{label}: {entry?.value}
</p>
<p style={{ margin: 0 }}>{point?.note}</p>
{/* The payload carries your original data point, so anything in it is available here. */}
<p style={{ margin: 0, borderTop: '1px dashed #f5f5f5' }}>Anything you want can be displayed here.</p>
</div>
);
}
export default function CustomizeTooltipContent() {
return (
<BarChart
style={{ width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
>
<XAxis dataKey="month" />
<YAxis />
<Tooltip content={RevenueTooltip} defaultIndex={2} active />
<Bar dataKey="revenue" />
<RechartsDevtools />
</BarChart>
);
}For smaller adjustments you rarely need content at all: formatter, labelFormatter, itemSorter and separator cover most of what people actually want to change.
Tooltip and Legend styling
Because they are HTML, these two take style objects rather than SVG attributes - and they have more than one slot:
- Tooltip:
wrapperStyle positions the floating box, contentStyle paints it, labelStyle is the header and itemStyle is one data row. - Legend:
wrapperStyle is the box and labelStyle is an individual entry. Beware the naming: labelStyle means the header on Tooltip and the entries on Legend.
import { Bar, BarChart, CartesianGrid, Legend, Tooltip, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
const data = [
{ month: 'Jan', revenue: 4200, profit: 1100 },
{ month: 'Feb', revenue: 5800, profit: 1500 },
{ month: 'Mar', revenue: 7200, profit: 2400 },
{ month: 'Apr', revenue: 6100, profit: 1800 },
{ month: 'May', revenue: 8900, profit: 3100 },
{ month: 'Jun', revenue: 7400, profit: 2600 },
];
export default function CustomizeLegendAndTooltipStyle() {
return (
<BarChart
style={{ width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid />
<XAxis dataKey="month" />
<YAxis />
{/*
* Tooltip has three style slots:
* wrapperStyle positions the floating box, contentStyle paints it,
* labelStyle is the header and itemStyle is one data row.
*/}
<Tooltip
defaultIndex={3}
contentStyle={{ backgroundColor: '#f8fafc', border: '2px solid #64748b', borderRadius: 8, padding: 10 }}
labelStyle={{ margin: 0, fontWeight: 700, color: '#0f172a' }}
itemStyle={{ display: 'block', paddingTop: 2, paddingBottom: 2 }}
/>
{/*
* Legend uses wrapperStyle for the box and labelStyle for the individual
* entries - note that `labelStyle` means the opposite thing here than it
* does on Tooltip.
*/}
<Legend
wrapperStyle={{
backgroundColor: '#f1f5f9',
border: '1px solid #cbd5e1',
borderRadius: 4,
paddingTop: 4,
paddingBottom: 4,
}}
labelStyle={{ color: '#0f172a', textTransform: 'uppercase', letterSpacing: '0.05em' }}
iconType="circle"
/>
<Bar dataKey="revenue" barSize={20} />
<Bar dataKey="profit" barSize={20} />
<RechartsDevtools />
</BarChart>
);
}Your own elements inside the chart
Since Recharts 3, a chart renders arbitrary children. Your own component can sit next to Bar and XAxis and draw whatever it wants. The old Customized wrapper is no longer needed and is deprecated - render your component directly.
What makes this useful is that chart state is available through hooks. usePlotArea tells you where the drawable area ended up after axes and legends took their space; useActiveTooltipDataPoints, useActiveTooltipLabel and useActiveTooltipCoordinate follow the pointer; useChartWidth, useChartHeight, useMargin and useOffset describe the layout. Recharts exports scale and domain hooks too, so a custom element can convert data values to pixels exactly the way the built-in components do.
import { Bar, BarChart, CartesianGrid, Tooltip, useActiveTooltipDataPoints, usePlotArea, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
type DataPoint = { month: string; revenue: number };
const data: Array<DataPoint> = [
{ month: 'Jan', revenue: 4200 },
{ month: 'Feb', revenue: 5800 },
{ month: 'Mar', revenue: 7200 },
{ month: 'Apr', revenue: 6100 },
{ month: 'May', revenue: 8900 },
{ month: 'Jun', revenue: 7400 },
];
/**
* `usePlotArea` reports where the drawable area sits after axes and legends
* have taken their space, so custom elements can position themselves against it.
*/
function PlotAreaFrame() {
const plotArea = usePlotArea();
if (plotArea == null) {
return null;
}
return <rect {...plotArea} fill="none" stroke="#64748b" strokeDasharray="4 4" />;
}
/**
* Chart state is available to any component inside the chart, so you can render
* your own readouts without going through Tooltip at all.
*/
function ActiveReadout() {
const plotArea = usePlotArea();
const activePoints = useActiveTooltipDataPoints<DataPoint>();
const point = activePoints?.[0];
if (plotArea == null || point == null) {
return null;
}
return (
<text x={plotArea.x + 10} y={plotArea.y + 22} fill="#0ea5e9" fontSize={16} fontWeight={700}>
{point.month}: {point.revenue}
</text>
);
}
export default function CustomizeCustomElement() {
return (
<BarChart
style={{ width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid />
<XAxis dataKey="month" />
<YAxis />
<Tooltip defaultIndex={4} />
<Bar dataKey="revenue" />
{/* Your own components are ordinary chart children - no wrapper component needed. */}
<PlotAreaFrame />
<ActiveReadout />
<RechartsDevtools />
</BarChart>
);
}Custom elements are yours, which means the theme does not style them. If you want them to follow the active theme, read it with useRechartsTheme - see the theming guide. To control what draws on top of what, see the z-index guide.
Where to go next