Skip to main content

ChartLegend

Chart Partschart

Series key with label, value and an optional progress bar; pattern-aware under decoration.

import { ChartLegend } from "@elabs-ai/components-charts";
Install
npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts

Skipping the @source line is the most common first failure: Tailwind does not scan node_modules, so every component renders unstyled. The one-time wiring

Prompt for your coding agent

Paste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.

Add the brand-ui ChartLegend chart (from @elabs-ai/components-charts) to this project.

1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`.
2. Look ChartLegend up before writing code: `npx -y @elabs-ai/components-cli docs ChartLegend` prints its real props, variants, what it pairs with and what to avoid.
3. If @elabs-ai/components-charts is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`.
4. Use it here: [the screen or file, and what it should show]
5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop.
6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.

Avoid

  • Dropping the legend on a multi-series chart — colour without a key is not a legend.
  • Ordering legend items differently from the series — the eye maps them positionally.
  • Passing a raw hex as the item colour — resolve the chart token so high decoration gets its pattern swatch.

Works with

Pairs with

Examples

5 live examples

With Progress

With progress bars and percentages.

Interactive

Click (or Tab + Enter) a legend entry to filter.

Toggle

Toggle a series off (Click, or Tab + Enter/Space) — `aria-pressed` flips, the label dims and strikes through.

Density comparison

#394: the percentage span reads the `text-meta` ROLE (was the raw `text‑xs` utility, which `data-density`/#340 cannot reach). Two columns pin `data-density` on a plain wrapping div — NOT `<ThemeProvider>`, which writes `data-density` to `document.documentElement` and would race two columns — mirroring `Foundations/Typography → Density scale`'s pattern. Must match `Gantt`'s already-density-aware timescale tick (11.25px compact / 12px comfortable / 12.75px spacious, styling-and-tokens.md).

API

ChartLegend

PropTypeDescription
itemsrequiredLegendItem[]Legend items to display
hoveredIndexnumber | nullCurrently hovered index (for highlight effect)
onHover(index: number | null) => voidCallback when an item is hovered
onItemClick( item: LegendItem, index: number, event: React.MouseEvent | React.KeyboardEvent, ) => voidDrill-down on a legend entry (#349). When set, each item renders as a real `<button>` — keyboard operable and named by the item's label — instead of a `<div>`. Unset, the legend is unchanged (a non-interactive `<div>` list). NOTE this lives on `ChartLegend`, not on the chart containers: the legend is a separately-placed composition primitive, so a callback on the container would be a prop the container cannot honour.
hiddenKeysReadonlySet<string>Series toggle (RM-118, `legend={{ interactive: "toggle" }}`). Set together with `onToggleKey`: every item renders as a real `<button aria-pressed>` — native keyboard support, no extra wiring — and `hiddenKeys` marks which ones read `aria-pressed="false"` and paint dimmed with a struck-through label (WCAG 1.4.1: hidden is never colour-only). Independent of `onItemClick` (drill-down); a container sets one or the other.
onToggleKey(key: string, item: LegendItem, index: number) => voidToggles one item's hidden state. Keyed by `item.key ?? item.label`.
toggleControl"item" | "checkbox"What hides an item when `onToggleKey` is set. `"item"` (default): the whole entry is the `aria-pressed` toggle. `"checkbox"`: the entry stays free for `onItemClick` (a host's selection, say) and a small checkbox after it toggles visibility. The checkbox shows while the row is hovered or holds focus, and always on touch screens, which cannot hover. A hidden entry stays in the legend, muted and struck through, in both modes.
hideAtDensityreadonly ChartDensity[]Density tiers that hide the whole legend (RM-072 default: `xs`/`sm`). The container legend engine (`useContainerLegend`, RM-118) passes `["xs"]` — `sm` renders `stack`-laid-out instead of hiding — so this stays `["xs", "sm"]` for every OTHER (direct) caller, unchanged.
showProgressbooleanShow progress bars. Default: false
showMarkerbooleanShow color marker dot. Default: true
showValuebooleanShow numeric value column. Default: true
showPercentagebooleanShow percentage value. Default: true when showProgress is true
formatValue(value: number) => stringFormat function for displaying values. Overrides `valueFormat` entirely when set.
valueFormatChartValueFormatHow to format `value`/`maxValue` (RM-109) — a preset or the object form (`{ decimals, abbreviate, sign, prefix, suffix, … }`, `value-format.ts`). Resolved as a SET across every item (#250, `useChartValueSetFormatter`) so the legend never mixes "1K" beside "400". Default: locale-aware plain grouping (`intFmt`), unchanged from before this prop existed.
currencystringISO 4217 code for `valueFormat: "currency"`. Falls back to `ChartConfigProvider`, then `"USD"`.
maxFractionDigitsnumberMost digits after the decimal point in the value column (RM-187).
localestringLocale for the value and percent columns (RM-187); unset, the `LocaleProvider`'s.
titlestringTitle shown above the legend
layout"row" | "stack" | "split"Item flow direction (RM-118, sitting 3). `"stack"` (default — every caller before this prop existed) is byte-identical to before: one item per line. `"row"` wraps items left-to-right (`flex-wrap`), matching the layout `AutoChart`'s old `AutoLegend` used, AND drops the `w-full` an interactive (`onItemClick`/toggle) item otherwise gets — `w-full` inside a row would force every item onto its own line regardless of this prop. `useContainerLegend` (the container legend engine) passes its own resolved `layout` ("row" at `base`/`medium`/`wide`, "stack" at `narrow` or density `sm`) through; a direct caller may set its own or leave it unset for the original stacked behaviour.
classNamestringAdditional class name for the container
titleClassNamestringClass name for the title
itemClassNamestringClass name for each legend item
labelClassNamestringClass name for the label
valueClassNamestringClass name for the value
renderItem(props: { item: LegendItem; index: number; isHovered: boolean; isFaded: boolean; percentage: number; }) => ReactNodeCustom render function for legend items. `item.value` may be `NaN`, meaning the item has no value (a container legend entry with no number of its own): check `Number.isFinite(item.value)` before printing it. `percentage` is 0 for such an item.
splitGroupsreadonly ChartLegendSplitGroup[]`layout="split"` (RM-121): one row per value axis, each led by its side label. Items not named by any group are omitted. Unset, `"split"` renders as `"row"`.
splitStackedbooleanStack the split rows vertically (the narrow tier). Default `false`: side by side, wrapping.