import { TreemapChart } 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 TreemapChart 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 TreemapChart up before writing code: `npx -y @elabs-ai/components-cli docs TreemapChart` 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
- What is it made of? Hierarchy and share.
- a nested hierarchy sized by one measure
Avoid
- the hierarchy has fewer than 2 levels — a flat bar chart is clearer
- Comparing a leaf's area against a leaf in a different group — area is only comparable WITHIN one parent's total; two leaves under different groups are fractions of different denominators, so their tile sizes are not commensurate across the group boundary.
- Reaching for palette: 'categorical' to encode a second measure on top of area — the hue there still only labels the group a leaf already belongs to (structurally, via the band); asking it to carry an unrelated variable makes one channel do two jobs and defeats the mono default's greyscale-legible design.
- Leaving a long tail of small leaves unmerged until tiles fall below labelMinArea — set otherThreshold before that happens (the unconditional 30-leaf cap is a backstop, not a substitute for tuning it); a treemap of unlabeled slivers reads as noise, not a breakdown.
- Turning on drilldown as the only way to reach a leaf's value — the group-band zoom button is a SEPARATE affordance from onDatapointClick on purpose; a leaf stays selectable by its own click/Enter without first forcing a reader to drill in.
Works with
Examples
Default
Loading
`status="loading"` (RM-184): the skeleton + `ChartLoadingLabel`, until the data arrives.
Mono
Default palette: one shared neutral shade per leaf — groups read apart only by their title band + paper gap, so the chart stays legible in greyscale.
Categorical
One hue per top-level group (≤ 4 groups); leaves inherit their group's hue.
Sequential
Leaf shade encodes the leaf's own value on the sequential ramp.
Legend, categorical groups
One row per top-level group, colour-keyed to the same hue each group's leaves/title band paint. Hovering a row dims every OTHER group's tiles.
Legend, sequential ramp
`palette="sequential"` has no discrete key — it renders the shared `RampLegend` instead.
Depth: 1 (flat)
Flat, single-level rendering — root's children only, no title bands.
Other Threshold
A long tail of small leaves merges into a single "Other" tile once each falls below 5% of its group's total.
Show Values
`labels` (#247) prints each tile's value under its name, so the quantity survives a screenshot, an export and a keyboard user — not only a hover. A tile too short or narrow for the whole number keeps its name and drops the value; values share one notation across the set. Off by default.
Show Values Other Threshold
The folded "Other" tile with its value printed — how much the long tail adds up to.
Drilldown
Click (or Tab to and press Enter on) a group's title band to zoom into it; the "← Work" control zooms back out. This is a SEPARATE affordance from `onDatapointClick` — the static stories above have no click handler beyond it.
Interactive
Click a leaf — or Tab in and press Enter — to report its datum below.
Selection states
`selectionStates` paints the host’s tri-state on leaf tiles: selected marks carry a compound foreground/background outline, excluded marks dim AND carry a full-opacity dashed frame, so the three states stay distinguishable in greyscale.
API
TreemapChart
| 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 | TreemapNode | The hierarchy. A leaf needs a `value`; a parent's explicit `value` (if any) must equal the sum of its children (dev-validated — see `validateTreemapData`). |
depth | 1 | 2 | How many levels of rectangles to render. `1` = flat (root's children only, no title bands). `2` = groups + leaves. Default `2`. |
palette | TreemapPalette | - `"mono"` (default) — one neutral shade for every leaf; groups separated only by the title band + gap. - `"sequential"` — leaf shade encodes the leaf's own value. - `"categorical"` — one hue per top-level group, ≤ 4 groups (else `"mono"`). |
gap | number | Paper seam between tiles, in px. Default `2`. |
labelMinArea | number | Hide a tile's label below this area (px²). Never shrinks type — a label either renders at `text-chart-value`/`text-chart-source` size, or not at all. |
labelOverflow | "ellipsis" | "hide" | How a name too long for its tile is handled (#280). - `"ellipsis"` (default) — clip it to the tile with `…`. - `"hide"` — draw it only when the WHOLE name fits (and, with `labels`, its value line too); otherwise draw nothing. Applies to leaf labels and group title bands alike — a truncated header is the same noise as a truncated tile name. |
hideLeafLabel | (leaf: TreemapLeafDatum) => boolean | Skip the native name/value label for every leaf this predicate matches (#280) — for a tile a caller annotates itself (a `Leader` + `HaloText` callout) so the two labels never double up. Default `undefined` — every eligible leaf keeps its built-in label. |
monoLeafColor | string | Override for `palette="mono"`'s one leaf shade (#280). The mono ramp is the same absolute lightness in every theme by design (`on-mark-ink.ts`), so it can look pale on a dark card; resolve a theme-appropriate step (`resolveThemeIsDark` + `var(--chart-mono-N)`) and pass it here when the default reads too light against your card. Default: the shared constant. |
monoBandColor | string | Override for the `depth: 2` group title band's fill — same reasoning as {@link monoLeafColor}. |
otherThreshold | number | Merge leaves under this share (0..1) of their parent's total into "Other". `0` (default) = off — the unconditional 30-leaf cap still applies. |
drilldown | boolean | When `true` (and `depth: 2`), clicking a group's title band zooms into that group (its leaves fill the canvas, animated); a "Back" control returns to the overview. Default `false` — a static chart has no click handler beyond `onDatapointClick`. |
showValues | boolean | @deprecated Use `labels` — `true`/`false` keep meaning the same thing (ADR 0042 A.3, row 15). Read until 7.0.0, with one development warning; when both are set, `labels` wins. |
labels | boolean | ChartDataLabelsConfig | Print each labelled tile's formatted value on a second line under its name (#247). A tile shows the value only when its name is drawn AND it is tall enough for two lines and wide enough for the whole number — a value is never ellipsised or shrunk, it is either legible or absent. All visible values share ONE notation (the set is formatted together). Default `false` — area stays the only printed quantity unless you opt in. |
valueFormat | ChartValueFormat | How leaf values render in the tooltip and (with `labels`) on tiles. Default `"compact"`. |
className | string | |
style | CSSProperties | |
aspectRatio | string | Aspect ratio as "width / height". Default `"16 / 9"`. |
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. |
accessibleLabel | ChartA11yProps["accessibleLabel"] | Accessible name for the chart region (announces to AT on focus). |
accessibleDescription | ChartA11yProps["accessibleDescription"] | Supplemental description read by AT. |
legend | ContainerLegendProp | Renders a legend (RM-118). What it shows depends on `palette`: - `"categorical"` — one row per top-level GROUP, via `useContainerLegend` (placement + hover only, R3): hovering a row dims every OTHER group's tiles, never hides one. `depth: 2`'s title bands already NAME each group on the tile itself — this adds the colour key, not a second name. `{ values: true }` prints each group's total, in `valueFormat`. - `"sequential"` — leaf shade encodes a continuous VALUE, not a discrete category, so this renders `RampLegend` (the shared ramp key every sequential/diverging consumer uses — `ramp-legend.tsx`) instead, with its marker following the hovered leaf's value. - `"mono"` (default) — every leaf is the SAME shade; there is nothing to key, so a truthy `legend` renders nothing. Unset renders nothing (R1). |
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. |
selectionStates | ChartSelectionStatesResolver<Record<string, unknown>> | Maps a mark (category, optional series key, optional raw row) to its selection state. Unset → the chart renders exactly as before. |
dimExcluded | boolean | Dim `excluded` marks and add their non-hue channel (the dashed frame). Default `true`; `false` keeps the `data-selection` attribute (so a host can style it) but paints nothing. |
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. |
ChartTooltipBox
| Prop | Type | Description |
|---|---|---|
xrequired | number | Anchor X in pixels, relative to the container — a crosshair, a mark centre. |
yrequired | number | Anchor Y in pixels, relative to the container. |
visiblerequired | boolean | Whether the tooltip is visible |
containerRefrequired | RefObject<HTMLDivElement | null> | The chart container: the box is portaled into it and placed around it. |
containerWidthrequired | number | Container width in the chart's own pixels (scales `x`/`avoid` to the screen). |
containerHeightrequired | number | Container height in the chart's own pixels. |
offset | number | Space kept between the box and the pointer or marks it avoids. Default 16. |
className | string | Custom class name |
childrenrequired | React.ReactNode | Tooltip content |
left | number | ReturnType<typeof useSpring> | @deprecated Bypasses placement: the box is positioned inside the container at this left, as before placement existed, and can cover the pointer. |
top | number | ReturnType<typeof useSpring> | @deprecated Bypasses placement: the box is positioned inside the container at this top, as before placement existed, and can cover the pointer. |
flipped | boolean | @deprecated Only read with `left`/`top`: the side the entrance slides from. |
springConfig | SpringConfig | Per-chart override; falls back to `ChartConfigProvider.tooltipBoxSpring`. |
panelStyle | React.CSSProperties | Inline styles for the inner tooltip panel. |
avoid | ChartTooltipAvoid | readonly ChartTooltipAvoid[] | The hovered mark(s) the box must not cover — a dot, a bar's band, a cell, a node. Small marks are stepped around; a mark too large to avoid (a big treemap leaf) is covered by at most half. The pointer and a keyboard-focused target are always avoided without being listed here. |
track | ChartTooltipTrack | How the anchor moves: `"x"` for a crosshair scrubbing along x (the box keeps its top at `y`), `"y"` for rows, `"free"` (default) for a mark anywhere in the plot. |
pinned | boolean | Freezes the placement while a touch tooltip is pinned (the finger has lifted). |
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 |
|---|---|
| band | var(--chart-mono-2) — always neutral, even under palette: 'categorical', so grouping reads from the title band + gap alone, never from hue |
| leafMono | var(--chart-mono-4) — one shared shade for every leaf (the default) |
| leafSequential | --chart-seq-1 … --chart-seq-7 by the leaf's own value |
| leafCategorical | one hue per top-level group (≤ 4 groups, else falls back to mono) |
| label | HaloText: the band title renders in the chart-source type role, the leaf label in the chart-value type role (RM-019, both pinned to the meta size step); the halo punches from var(--chart-background) |
| focus | the drilldown zoom/back buttons carry the shared compound focus indicator (--ring plus the --ring-contour outline), shown only on keyboard focus |