Skip to content

Chart legend

Lists a chart's series with their values beside it, or the sizes of its bubbles.

Campaigns by source
  • Email: 35 (35%)
  • Call: 25 (25%)
  • Text: 20 (20%)
  • Social: 12 (12%)
  • Phone: 8 (8%)

Installation

pnpm dlx shadcn@latest add @wisty/chart-legend

The CLI installs the Radix or Base UI file to match your project, with what it builds on: chart.

If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.

When to use

Use it

  • A donut or a bar chart's legend should show each entry's value, like "Email 35 (35%)".
  • A bubble chart uses dot size for a measure, and readers need to know what the sizes mean.
  • A chart needs its legend outside the chart's box, like a list beside a donut in a wide card.

Don't use it

  • For a plain legend under a chart. Use ChartLegend with ChartLegendContent from Chart, inside the chart.
  • For a chart with one series. Name the measure in the card title instead.
  • For exact values readers need to compare. Add a table beside the chart.

Usage

Both parts sit beside a ChartContainer, not inside it, and read the same config.

import { type ChartConfig, ChartContainer } from "@/components/ui/chart"
import { ChartLegendList } from "@/components/wisty/chart-legend"

const config = {
  won: { label: "Closed Won", color: "var(--chart-1)" },
  open: { label: "Open", color: "var(--chart-2)" },
  lost: { label: "Closed Lost", color: "var(--chart-3)" },
} satisfies ChartConfig
<figure>
  <ChartContainer config={config}>{/* the chart */}</ChartContainer>
  <ChartLegendList
    config={config}
    keys={["won", "lost"]}
    aria-label="Outcome"
  />
</figure>

A list with values, standing beside a donut or a funnel, is in the Campaigns by source and Funnel with values examples. Format the values for the org's locale and currency; the examples use en-US and USD.

ChartSizeLegend must agree with the chart's ZAxis, so work out one domain and one range and pass both to each:

import { ZAxis } from "recharts"

import { ChartSizeLegend } from "@/components/wisty/chart-legend"

const rows = [{ deals: 56 }, { deals: 12 }, { deals: 3 }]
// From 0 to the largest row, so the domain covers every row.
const sizeDomain: [number, number] = [
  0,
  Math.max(0, ...rows.map((row) => row.deals)),
]
const sizeRange: [number, number] = [0, 900]
<ZAxis type="number" dataKey="deals" domain={sizeDomain} range={sizeRange} />
<ChartSizeLegend title="Open deals" domain={sizeDomain} range={sizeRange} />

Examples

Bubble chart

Relationship. Dot size is open deals, explained by `ChartSizeLegend` from the `ZAxis` `domain` and `range`, and color is industry in `ChartLegendList`.

Accounts by employees, sales and open deals
USD
  • Technology
  • Healthcare
  • Retail
Open deals by dot size: larger dots mean more. The legend shows dots for 56, 20 and 2.

Funnel with values

Pipeline. A funnel with square swatches from `shape`, each with its stage's count and share, since the arrow keys can't walk stages.

Opportunities by stage
  • Prospecting: 120 (37%)
  • Qualification: 90 (28%)
  • Proposal/Price Quote: 60 (18%)
  • Negotiation/Review: 35 (11%)
  • Closed Won: 20 (6%)

API reference

PartBuilt on
ChartLegendList<ul> (opens in a new tab)
ChartSizeLegend<div> (opens in a new tab)

ChartLegendList

PropTypeDefaultDescription
config*ChartConfigThe chart's config. Its labels and colors make the list.
keysstring[]Which entries to show, in this order. Default every entry with a color, in config order.
valuesRecord<string, React.ReactNode>A formatted value per key, shown after its label, like "35 (35%)".
shape"dot" | "square""dot"Dots for lines, bars and slices; squares for square marks.
orientation"horizontal" | "vertical""horizontal"A wrapping row, or a column with values at the end.

ChartSizeLegend

PropTypeDefaultDescription
title*stringWhat dot size measures, like "Deals".
domain*[number, number]The ZAxis domain, covering every row: Recharts widens one that doesn't, unseen here.
range*[number, number]The ZAxis range: the smallest and largest dot area, in square pixels.
valuesnumber[]The values to draw, largest three. Default the domain's end and round values below it.
valueFormatter(value: number) => stringStringFormats each value, like (v) => count.format(v).

SLDS mapping

SLDS has no legend markup. Its Charts guidelines show legends as swatches with labels, sometimes with values, and a size legend for bubble charts. ChartLegendList draws the same swatches as Chart's ChartLegendContent and its tooltip dot. ChartSizeLegend is the guidelines' size legend: nested outlined circles sharing a bottom edge, with a level leader from each circle's top to its value, sized by Recharts' ZAxis scale.

Known gaps:

  • No gradient legend for color scales.
PropValueSLDSNotes
ChartLegendList(part) (added)none (SLDS Charts legend)A list of the config's entries in config order, with optional values.
ChartLegendList shapedot, square (added)noneDots for lines, bars and slices; squares for square marks.
ChartLegendList orientationhorizontal, vertical (added)noneA wrapping row below a chart, or a column beside it with values at the end.
ChartSizeLegend(part) (added)none (SLDS Charts size legend)Up to three nested circles that say what dot size means, on the ZAxis's linear scale, with level leaders to their values.

Accessibility

  • Give ChartLegendList an aria-label that says what the entries are, like "Industry". It names the list, not the chart; name the chart with its card title.
  • Each entry reads as its label and value, like "Email: 35 (35%)". The swatch is hidden from screen readers, so an entry's meaning must be in its label.
  • ChartSizeLegend hides its drawing and title and says the same in a sentence ("Open deals by dot size: larger dots mean more. The legend shows dots for 56, 20 and 2."), so title must read as the measure's name.

Responsive behavior

  • A horizontal list wraps onto new lines and centers its entries.
  • A vertical list beside a donut needs room: about 400px for the chart and the list together. In a narrower card, put the list under the chart with orientation="horizontal".

Rules

  • Put ChartLegendList and ChartSizeLegend beside ChartContainer, never inside ChartLegend's content.
  • Write config colors as var(--chart-n), a literal or theme, in order from --chart-1. Never var(--color-<key>): that variable exists only inside the chart's container, so the legend's swatch would be empty. Rows and series inside the chart may still use it.
  • Always set the ZAxis domain yourself, covering every row. A domain the data outgrows is widened by the chart, and the legend's circles no longer match the dots.
  • Pass values to ChartSizeLegend only with clearly different sizes. It draws the three largest, and two close values put one label on the other.
  • Draw a bubble chart's points in one Scatter, with fill: "var(--color-<key>)" on each row. The arrow keys only walk a chart's first Scatter, so one per color leaves points out of reach. Name the color's category in the tooltip too.
  • Never pass asChild or render.