Skip to main content

BumpChart

Change over Timechart

Rank over discrete time — who is #1 changes over time. variant="lines" plots the trajectory (rank as an inverted y-axis); variant="strip" is the G21 filmstrip (fixed rows, shade + a printed number carry the per-period rank).

import { BumpChart } 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 BumpChart 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 BumpChart up before writing code: `npx -y @elabs-ai/components-cli docs BumpChart` 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 did it change over time? Rank over time.
  • rank of several entities over ordered periods

Avoid

  • only 2 periods — use a dumbbell chart
  • Passing neither valueKey nor rankKey — there is no honest rank to derive, and every row is silently dropped rather than plotting a guessed order.
  • Mixing rankKey and valueKey inconsistently across periods — rankKey, when present, wins outright per row; a dataset that supplies rank for some periods and expects value-derived rank to fill the rest will jump between two different orderings.
  • Choosing variant="lines" for more than ~10-12 entities — crossing lines stop being followable well before maxEntities' default cap of 10 kicks in; variant="strip" reads a crowded field better because rows never move.
  • Reading the strip's row POSITION as the current rank — rows are fixed at each entity's FINAL rank; only the cell shade and its printed number carry the rank at any other period.

Works with

Contains

Examples

7 live examples

Default

`variant="lines"` (default) — rank is derived from `share`; Atlas overtakes Nimbus in Q2 and the two lines visibly cross.

Loading

`status="loading"` (RM-185): a skeleton fills the same plot box the ready chart would use, at every width, so nothing moves once the data lands.

Highlighted

`highlightKey` draws one entity in ink (bold end labels), the rest on the neutral mono ladder — the "one line is the point" read.

Rank Strip

G21 Rank Strip — "Flows climbs to the top": rows stay fixed at final rank, shade + the printed number carry the per-quarter rank, the hero row (Flows) is bold and its delta flag reads ▲3.

Lines Many Entities

8 entities × 6 periods in a short plot — eight end labels cannot sit `END_LABEL_MIN_GAP` px apart, so the chart keeps only the top and bottom rank (plus the hero, when set) and leaves the rest to the tooltip and datapoint layer (#281). Every label it does draw stays legible and inside the plot.

Rank Strip Dense Grid

A `strip` cell never renders its fill without also rendering its rank (WCAG 1.4.1) — even at 14 periods in a 480px box, where the raw column width would otherwise be too narrow for a legible numeral.

Keyboard Drilldown

Every plotted point is a keyboard datapoint target — a real `<button>` in a `pointer-events: none` layer BESIDE the `aria-hidden` SVG, never inside it. The target's accessible name carries the RANK, the chart's entire visual encoding — never only the metric a `valueKey` supplies (#270).

API

BumpChart

PropTypeDescription
messagesChartMessagesmessages 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.
datarequiredRecord<string, unknown>[]Long-format data — one row per (period, entity) pair.
periodrequiredstringKey in `data` for the discrete period (e.g. "Q1", "2026-W12").
entityrequiredstringKey in `data` for the entity being ranked.
valueKeystringKey in `data` for the entity's value that period. Ranks are derived from it when `rankKey` is absent.
rankKeystringKey in `data` for an already-computed rank (1 = best). Takes priority over a derived rank when present.
variantBumpVariant`"lines"` (default) — one line per entity on an inverted rank y-axis. `"strip"` — G21's fixed-row filmstrip: shade + a printed number carry the per-period rank instead of vertical position.
highlightKeystring | numberThe hero entity — drawn in ink (`var(--chart-foreground)`) and bold; every other entity draws from the neutral mono ladder. A string or number is matched against the row's `entity` value (`==`-free, via `String(…)`), the same rule `Bar`'s `highlightKey` uses.
showDeltabooleanShow a ▲n / ▼n delta flag (rank movement into the LAST period) per row. Default `false`.
maxEntitiesnumberCap on plotted entities, kept by final rank. Default `10`; dev-warns once when the data has more. For `variant="strip"` this is also intersected with the largest row count whose printed rank still fits legibly (#273) — a raised `maxEntities` that would no longer fit is quietly lowered and warned about by name, never silently dropped to colour-only.
maxPeriodsnumber`variant="strip"` only: cap on plotted periods (columns), kept as the MOST RECENT N — a rank race reads right-to-left, so the oldest periods are the meaningful ones to drop. Defaults to the largest column count whose printed rank still reaches the legibility floor for the chart's current width; dev-warns once naming `maxPeriods` when data (or an explicit value) would cross it (#273). Ignored by `"lines"`.
paletteChartPaletteWhich colour family the marks draw from. `"strip"`: the cell shade, default `"sequential"` (rank 1 = most ink). `"lines"`: one colour per entity, default `"categorical"`; ignored while `highlightKey` names an entity, which draws hero-ink + mono.
valueFormatChartValueFormatHow the tooltip's raw value cell is formatted. Default `"compact"`.
marginnumber | Partial<Margin>Chart margins: one number for every side, or per side.
aspectRatiostringAspect ratio as `"width / height"`. Default `"2 / 1"`.
plotHeightResponsive<ChartPlotHeight>The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. Wins over `aspectRatio`, which stays an alias.
classNamestring
statusChartStatusLoading vs ready (RM-185). `"loading"` shows a skeleton in the plot box the chart will fill, with one polite status message, until the data is ready. Default: `"ready"`.
accessibleLabelChartA11yProps["accessibleLabel"]Accessible name for the chart region (announces to AT on focus).
accessibleDescriptionChartA11yProps["accessibleDescription"]Supplemental description read by AT (e.g. entity count + period range).
onDatapointClickChartDatapointClickHandler<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.
copyValueOnActivatebooleanPut 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.
datapointLabelChartDatapointLabel<Record<string, unknown>>Override the accessible name of each keyboard target.
maxInteractiveDatapointsnumberDev-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.

Also accepts: ChartInteractionProps, FrameSizeGroupProps, Pick<ChartStateGroupProps, "status">

HaloText

PropTypeDescription
halostringThe 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.
haloWidthnumberHalo width in px (default 3). The visible halo is half of it — a stroke is centred.

Also accepts: SVGProps<SVGTextElement>

QuietDot

PropTypeDescription
cxrequirednumberCell centre x.
cyrequirednumberCell centre y.
sizenumberDiameter in px (default 0.9). Raise it only for a very large cell.

Also accepts: Omit<SVGProps<SVGCircleElement>, "r">

Theming

The tokens each state resolves to. Change the token and every theme follows.

StateToken
herohighlightKey draws var(--chart-foreground) (ink) + bold labels; every other entity draws from the neutral mono ladder, never a second categorical hue
rankvariant="strip" cell shade = --chart-seq-1…7 via resolvePalette("sequential", maxRank) — rank 1 is always the most-ink step, whichever theme is active
deltashowDelta reads var(--success) for a climb (▲) and var(--destructive) for a drop (▼) via profitLossColor — colour reinforces the arrow glyph, it never stands alone
missinga period an entity has no row for draws a QuietDot pinprick, never a blank cell