Label colors

Inside a theme, data labels pick their text color automatically. A label next to its shape takes the color of the shape, so that it reads as part of the series. A label on top of a filled shape takes the pageBackground color of the theme, like a cut-out of the shape. If it took the color of the shape, it would be invisible.

import { Bar, CartesianGrid, ComposedChart, LabelList, Line, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

const data = [
  { month: 'Jan', desktop: 420, mobile: 240 },
  { month: 'Feb', desktop: 380, mobile: 310 },
  { month: 'Mar', desktop: 510, mobile: 290 },
  { month: 'Apr', desktop: 470, mobile: 400 },
];

const chartStyle = { width: '100%', maxWidth: 600, maxHeight: '70vh', aspectRatio: 1.618 };
const margin = { top: 24, right: 20, left: 0, bottom: 0 };
const lineLabel = { position: 'top' } as const;

/*
 * This chart has no theme provider of its own. It takes the theme of this website,
 * and the labels pick their colors from it:
 * - the label inside each bar takes the page background color,
 * - the label above each bar takes the color of the bar,
 * - the label of each Line point takes the color of the line.
 */
export default function LabelColorsExample() {
  return (
    <ComposedChart style={chartStyle} responsive data={data} margin={margin}>
      <CartesianGrid vertical={false} />
      <XAxis dataKey="month" />
      <YAxis />
      <Bar dataKey="desktop" isAnimationActive={false}>
        <LabelList dataKey="desktop" position="insideTop" />
        <LabelList dataKey="month" position="top" />
      </Bar>
      <Line dataKey="mobile" isAnimationActive={false} label={lineLabel} />
      <RechartsDevtools />
    </ComposedChart>
  );
}

This applies to data labels: LabelList, and the label prop of graphical items. Other labels, such as axis labels and the labels of reference lines and areas, take the typography color of the theme, or the top-level label theme.

Without a theme, nothing changes: data labels keep the colors they have always had.

The rules

LabelText color
On top of the shape of a Bar, Funnel, Pie or RadialBarpageBackground. If the theme has none, chart.backgroundColor, and if it has neither, the typography color.
Next to the shapeThe color the shape is drawn with, the same color as in the Tooltip and the Legend: its fill, or its stroke when the fill is none or transparent. If a data entry sets its own fill, its label takes that color.
Any label of a Line, Area, Radar or ScatterThe color of the series: the stroke of a Line, Area or Radar, and the color of each point in Scatter. These labels never sit on a filled shape.
With a background or a haloThe color of the shape. The label sits on its own background, not on the shape.
At an { x, y } positionThe typography color. Recharts cannot tell where such a label ends up.

Which positions are on top of the shape

A shape is only filled when its fill paints something. A Bar or RadialBar with fill="none" is an outline, so every label counts as next to it and takes its stroke.

In cartesian charts, top, left, right and bottom are next to the shape. Every other position, including no position at all, is on top of it. Note that outside is not a cartesian position: in a Bar or Funnel, it is drawn in the center.

In polar charts, outside is past the outer edge of the sector, end is past the end of a RadialBar, and center, centerTop and centerBottom are at the center of the chart. Every other position is on top of the sector, including insideStart and insideEnd.

import { LabelList, Pie, PieChart, RadialBar, RadialBarChart } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

const data = [
  { name: 'Chrome', value: 240 },
  { name: 'Safari', value: 200 },
  { name: 'Edge', value: 170 },
  { name: 'Firefox', value: 150 },
];

const rowStyle = { display: 'flex', flexWrap: 'wrap', gap: 16 } as const;
const chartStyle = { width: '100%', maxWidth: 300, aspectRatio: 1 };

/*
 * In polar charts, `outside` and `end` are away from the sector, and so are the
 * `center` positions, which are at the center of the chart.
 * Every other position is on top of the sector.
 */
export default function PieLabelColorsExample() {
  return (
    <div style={rowStyle}>
      <PieChart style={chartStyle} responsive>
        <Pie data={data} dataKey="value" nameKey="name" outerRadius="70%" isAnimationActive={false}>
          <LabelList dataKey="value" position="inside" />
          <LabelList dataKey="name" position="outside" />
        </Pie>
        <RechartsDevtools />
      </PieChart>
      <RadialBarChart
        style={chartStyle}
        responsive
        data={data}
        innerRadius="20%"
        outerRadius="90%"
        startAngle={90}
        endAngle={-180}
      >
        <RadialBar dataKey="value" isAnimationActive={false}>
          <LabelList dataKey="name" position="insideStart" />
          <LabelList dataKey="value" position="end" />
        </RadialBar>
        <RechartsDevtools />
      </RadialBarChart>
    </div>
  );
}

The default label of a Pie, which you get with label={true}, is drawn outside of the sector, so it takes the color of the sector.

Contrast

Every color of the built-in palettes has at least 4.5:1 contrast against the pageBackground of its theme, the WCAG contrast for normal text. That covers labels next to their shape. A label on top of a shape uses the same two colors the other way around. The built-in themes paint fills slightly transparent, so on some colors the contrast there is a little lower, but still above 3:1.

To bring labels on top of palette colors to 4.5:1, draw a background or a halo. The label then sits on the page color and takes the color of its shape, which is the pair the palette is designed for.

Colors that come from your data or your own theme can be anything, and a background does not help with them. A pale fill has little contrast with the page, so a label in the page color is hard to read on top of it, and a label in the pale color is hard to read on a page-colored background. Set the fill of the label to a color that contrasts with your data colors instead.

palette colors + background
pale data colors
pale data colors + fill
import { ReactNode } from 'react';
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';

const paletteData = [
  { name: 'A', value: 420 },
  { name: 'B', value: 380 },
  { name: 'C', value: 510 },
];

/*
 * Pale colors from the data. They have little contrast with a white page,
 * so neither a label in the page color nor a label in the bar color is readable.
 */
const paleData = [
  { name: 'A', value: 420, fill: '#fde047' },
  { name: 'B', value: 380, fill: '#a7f3d0' },
  { name: 'C', value: 510, fill: '#bfdbfe' },
];

const rowStyle = { display: 'flex', flexWrap: 'wrap', gap: 16 } as const;
const figureStyle = { margin: 0, width: '100%', maxWidth: 260 };
const chartStyle = { width: '100%', aspectRatio: 1 };
const margin = { top: 10, right: 10, left: 0, bottom: 0 };

function SmallBarChart({ data, children }: { data: ReadonlyArray<object>; children: ReactNode }) {
  return (
    <BarChart style={chartStyle} responsive data={data} margin={margin}>
      <CartesianGrid vertical={false} />
      <XAxis dataKey="name" />
      <YAxis width={40} />
      <Bar dataKey="value" isAnimationActive={false}>
        {children}
      </Bar>
      <RechartsDevtools />
    </BarChart>
  );
}

export default function LabelBackgroundExample() {
  return (
    <div style={rowStyle}>
      <figure style={figureStyle}>
        {/* Palette colors: the background puts the label on the page color, in the bar color */}
        <SmallBarChart data={paletteData}>
          <LabelList position="inside" background />
        </SmallBarChart>
        <figcaption>palette colors + background</figcaption>
      </figure>
      <figure style={figureStyle}>
        {/* Pale data colors: the automatic color is hard to read in the light theme */}
        <SmallBarChart data={paleData}>
          <LabelList position="inside" />
        </SmallBarChart>
        <figcaption>pale data colors</figcaption>
      </figure>
      <figure style={figureStyle}>
        {/* Pale data colors: dark text is readable on every pale bar, in both themes */}
        <SmallBarChart data={paleData}>
          <LabelList position="inside" fill="#18181b" />
        </SmallBarChart>
        <figcaption>pale data colors + fill</figcaption>
      </figure>
    </div>
  );
}

A background draws a rectangle behind each label. It is sized after the label renders, so it is not part of server-side rendering. A halo draws an outline around the letters instead: it needs no measuring, so it is drawn during server-side rendering, and it covers less of the chart. Both use pageBackground unless you set a color.

To turn backgrounds on for every data label, set label.background in the theme:

import { type RechartsTheme, lightTheme } from 'recharts';

const theme: RechartsTheme = {
  ...lightTheme,
  graphicalItems: lightTheme.graphicalItems.map(item => ({
    ...item,
    // Every data label gets a background, and so takes the series color
    label: { background: { rx: 4 } },
  })),
};

Choosing your own color

The automatic color is the last resort. In order, a data label takes:

  1. The fill prop of the LabelList, or of the object in the label prop.
  2. A fill or color in graphicalItems[n].label.style of the theme.
  3. The automatic color.
<Bar dataKey="desktop">
  {/* An explicit fill always wins */}
  <LabelList position="inside" fill="#000" />
</Bar>
import { type RechartsTheme, lightTheme } from 'recharts';

const theme: RechartsTheme = {
  ...lightTheme,
  graphicalItems: lightTheme.graphicalItems.map(item => ({
    ...item,
    // Every data label of this series takes this color, wherever it is
    label: { style: { fill: '#18181b' } },
  })),
};

Custom content receives the resolved color as its fill prop, so it can use it or ignore it:

import { LabelList, type LabelProps } from 'recharts';

function CustomLabel({ x, y, value, fill }: LabelProps) {
  // fill is the automatic color, unless the LabelList sets its own
  return (
    <text x={x} y={y} fill={fill}>
      {value}
    </text>
  );
}

<LabelList content={CustomLabel} position="inside" />