import { ParallelCoordinatesChart } 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 ParallelCoordinatesChart 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 ParallelCoordinatesChart up before writing code: `npx -y @elabs-ai/components-cli docs ParallelCoordinatesChart` 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.
Use it for
- How do these compare? Many entities, many dimensions.
- many numeric dimensions compared across entities at once
Avoid
- more than about 2 entities need per-entity detail — use small-multiple radar
- Fewer than 3 or more than 6 dimensions — below 3 a DumbbellChart (2) or MetricCard (1) reads better; above 6 the axes crowd and `resolveParallelDimensions` clamps to the first 6 with a console warning, silently dropping the rest.
- Dimensions on wildly different natural scales with no explicit `domain` — each axis normalizes independently, so an unset domain just uses that dimension's own min/max, which is usually right; only override when you need a domain that isn't in the data (e.g. a known 0-100 ceiling).
- Using `highlightKey` to try to promote MULTIPLE entities to hero status — only the first row a predicate matches (or the one literal id) is promoted; for comparing several entities at once, dim the rest via a different technique or fall back to small multiples.
- Reaching for this when the entities do NOT share the same dimensions, or when there is only one entity — a single-entity profile across many metrics is a RadarChart (shared 0-100 radial scale) or a MetricGrid, not a parallel-coordinates plot.
Works with
Examples
Default
Twelve products across four mixed-unit dimensions — the L20 acceptance recreation. No hero: every hairline draws at its seeded 0.5–0.8 opacity.
Loading
`status="loading"` (RM-184): the skeleton + `ChartLoadingLabel`, until the data arrives.
Hero Line
`highlightKey` as a literal entity id promotes ONE line to the 2px hero — drawn last (on top) with a halo label at its final axis point.
Hero By Predicate
`highlightKey` as a predicate promotes the first row it matches — here, the first product clearing an NPS of 50.
Show Extremes
`showExtremes` draws each axis's min/max value at its foot.
Monotone Curve
`curve="monotone"` draws a smoothed `curveMonotoneX` bézier between axes instead of a straight hairline segment.
Categorical Few Entities
Three axes (the minimum) with an explicit `categorical` palette, honoured up to the six-entity soft cap.
Keyboard Cycling
Setting `onDatapointClick` mounts the keyboard/pointer interaction layer — arrow keys cycle through every entity, Enter activates. Verifies the roadmap's "keyboard cycles through entities" acceptance bullet.
API
ParallelCoordinatesChart
| Prop | Type | Description |
|---|---|---|
messages | ChartMessages | messages group (RM-187): this chart's own words, keyed by the ui catalogue's `charts.*` message keys. A key set here wins over the `LocaleProvider`; every other key reads the catalogue as before. |
datarequired | Record<string, unknown>[] | Data array — one row per entity. |
entityrequired | string | Key in `data` for the entity label (drawn in the tooltip title and the hero halo label). |
dimensionsrequired | ParallelCoordinatesDimension[] | 3–6 axes, left to right. See {@link resolveParallelDimensions} for what happens outside that range. |
highlightKey | string | number | ((datum: Record<string, unknown>) => boolean) | Names the ONE entity promoted to the hero line — a string or number matched against `entity`'s column (`==`-free, via `String(…)`, the same rule `Bar`'s `highlightKey` uses), or a predicate over the row datum. `undefined` (default): no hero, every line draws at the same seeded hairline opacity. |
curve | ParallelCoordinatesCurve | `"linear"` (default) or `"monotone"` (curveMonotoneX) between axes. |
showExtremes | boolean | Draw each axis's min/max value at its foot. Default `false`. |
palette | ChartPalette | Which colour family entity lines draw from. Default `"mono"` — many hairlines read as a texture, not a legend, so `"categorical"` is honoured only up to `resolvePalette`'s own six-entity soft cap (past that it degrades to `"mono"` and warns, exactly like every other container — see `chart-context.tsx`). |
margin | Partial<Margin> | Chart margins. |
aspectRatio | string | Aspect ratio as `"width / height"`. Default `"2 / 1"`, and `"1.25 / 1"` at the narrow tier (`DEFAULT_CHART_PLOT_HEIGHT`). |
plotHeight | Responsive<ChartPlotHeight> | The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. Wins over `aspectRatio`, which stays an alias. |
className | string | |
accessibleLabel | ChartA11yProps["accessibleLabel"] | Accessible name for the chart region (announces to AT on focus). |
accessibleDescription | ChartA11yProps["accessibleDescription"] | Supplemental description read by AT (e.g. entity count + dimension list). |
status | ChartStatus | Show the loading skeleton until the data is ready (ADR 0042 §9). Default `"ready"`. |
empty | ChartEmptyState | Title and message shown when there is nothing to plot. |
onDatapointClick | ChartDatapointClickHandler<Record<string, unknown>> | Fires when a datapoint is activated by pointer OR keyboard. Setting it is what enables the interaction layer — with it unset the chart renders exactly as before, with no extra DOM and no new focusables. |
copyValueOnActivate | boolean | Put the datapoint's EXACT value on the clipboard when it is activated — the recovery path for compact axis labels and tooltips, reachable by mouse AND keyboard because activation rides `ChartDatapointLayer`'s real buttons. Default `false` on a raw chart container: turning it on mounts the interaction layer, and the documented guarantee is that a chart with no interaction props renders byte-identical DOM. `AutoChart` — the spec-driven surface, where the caller never sees the axis config — turns it on by default. A consumer-supplied `onDatapointClick` always wins; the two never both run. |
datapointLabel | ChartDatapointLabel<Record<string, unknown>> | Override the accessible name of each keyboard target. |
maxInteractiveDatapoints | number | Dev-warning threshold on the number of keyboard targets. Default 500. NOT a functional cap — every plotted point stays reachable, so a keyboard user can reach exactly what a mouse user can click. |
HaloText
| Prop | Type | Description |
|---|---|---|
halo | string | The halo colour. Defaults to `var(--chart-background)` — the plot ground — which is what makes the mark THEME-SAFE: on a light card the halo is light, on a dark card it is dark, with no `dark:` override and no literal anywhere. Override it only when the text sits on something other than the plot ground (a tooltip plate, a filled zone) — and then with another semantic token. The halo tracks the PLOT ground, not the card. The shipped themes declare that mirror in the token layer (`--chart-background: var(--card)` in `themes.css`), so the two agree by contract, not by coincidence. A theme that paints the plot on something other than `--card` must set `--chart-background` to that ground, or every halo punches a card-coloured hole in it. |
haloWidth | number | Halo width in px (default 3). The visible halo is half of it — a stroke is centred. |
DrawPath
| Prop | Type | Description |
|---|---|---|
duration | number | Draw duration in seconds (default 0.9). |
delay | number | Delay before drawing starts, in seconds — pair it with `stagger(i, …)`. |
transition | Transition | Full transition override. Wins over `duration`/`delay`. |
ChartDatapointLayer
| Prop | Type | Description |
|---|---|---|
label | string | Accessible name for the target group. Defaults to a localized generic name. |
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| axis | var(--chart-foreground-muted) vertical hairline with end ticks (HairlineFloor style) |
| extremeLabel | var(--chart-foreground-muted) on a var(--chart-background) halo, axis feet only when showExtremes |
| heroLine | 2px, var(--chart-foreground) or the resolved palette ink, opacity 1 — drawn LAST so it sits on top |
| nonHeroLine | 0.65px hairline, seeded 0.5-0.8 opacity — a texture of many lines, not individually legible |