Chart legend
Lists a chart's series with their values beside it, or the sizes of its bubbles.
Installation
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
ChartLegendwithChartLegendContentfrom 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`.
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.
API reference
| Part | Built on |
|---|---|
ChartLegendList | <ul> (opens in a new tab) |
ChartSizeLegend | <div> (opens in a new tab) |
ChartLegendList
| Prop | Type | Default | Description |
|---|---|---|---|
config* | ChartConfig | The chart's config. Its labels and colors make the list. | |
keys | string[] | Which entries to show, in this order. Default every entry with a color, in config order. | |
values | Record<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
| Prop | Type | Default | Description |
|---|---|---|---|
title* | string | What 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. | |
values | number[] | The values to draw, largest three. Default the domain's end and round values below it. | |
valueFormatter | (value: number) => string | String | Formats 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.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
ChartLegendList | (part) (added) | none (SLDS Charts legend) | A list of the config's entries in config order, with optional values. |
ChartLegendList shape | dot, square (added) | none | Dots for lines, bars and slices; squares for square marks. |
ChartLegendList orientation | horizontal, vertical (added) | none | A 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
ChartLegendListanaria-labelthat 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.
ChartSizeLegendhides 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."), sotitlemust 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
ChartLegendListandChartSizeLegendbesideChartContainer, never insideChartLegend'scontent. - Write config colors as
var(--chart-n), a literal ortheme, in order from--chart-1. Nevervar(--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
ZAxisdomainyourself, covering every row. A domain the data outgrows is widened by the chart, and the legend's circles no longer match the dots. - Pass
valuestoChartSizeLegendonly 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, withfill: "var(--color-<key>)"on each row. The arrow keys only walk a chart's firstScatter, so one per color leaves points out of reach. Name the color's category in the tooltip too. - Never pass
asChildorrender.