import { ChartLegend } from "@elabs-ai/components-charts";npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-chartsPaste 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
Used inside
Pairs with
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
| Prop | Type | Description |
|---|---|---|
itemsrequired | LegendItem[] | Legend items to display |
hoveredIndex | number | null | Currently hovered index (for highlight effect) |
onHover | (index: number | null) => void | Callback when an item is hovered |
onItemClick | ( item: LegendItem, index: number, event: React.MouseEvent | React.KeyboardEvent, ) => void | Drill-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. |
hiddenKeys | ReadonlySet<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) => void | Toggles 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. |
hideAtDensity | readonly 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. |
showProgress | boolean | Show progress bars. Default: false |
showMarker | boolean | Show color marker dot. Default: true |
showValue | boolean | Show numeric value column. Default: true |
showPercentage | boolean | Show percentage value. Default: true when showProgress is true |
formatValue | (value: number) => string | Format function for displaying values. Overrides `valueFormat` entirely when set. |
valueFormat | ChartValueFormat | How 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. |
currency | string | ISO 4217 code for `valueFormat: "currency"`. Falls back to `ChartConfigProvider`, then `"USD"`. |
maxFractionDigits | number | Most digits after the decimal point in the value column (RM-187). |
locale | string | Locale for the value and percent columns (RM-187); unset, the `LocaleProvider`'s. |
title | string | Title 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. |
className | string | Additional class name for the container |
titleClassName | string | Class name for the title |
itemClassName | string | Class name for each legend item |
labelClassName | string | Class name for the label |
valueClassName | string | Class name for the value |
renderItem | (props: { item: LegendItem; index: number; isHovered: boolean; isFaded: boolean; percentage: number; }) => ReactNode | Custom 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. |
splitGroups | readonly 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"`. |
splitStacked | boolean | Stack the split rows vertically (the narrow tier). Default `false`: side by side, wrapping. |