import { PieChart } 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 PieChart 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 PieChart up before writing code: `npx -y @elabs-ai/components-cli docs PieChart` 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? Share of a whole.
- parts of a whole across a few categories, read as proportions of the total
Avoid
- more than 5 slices — use a bar or unit chart
- More than ~5 slices — a bar chart compares better; a pie is not a ranking.
- A pie for values that don't sum to a meaningful whole.
- Identifying slices by color alone — put the label and value on or beside the slice.
Works with
Contains
Used inside
Examples
Default
Solid pie with token-driven slice colors (--chart-1..12)
Loading
Loading skeleton (RM-183) — shown while `status="loading"`, sized like the real chart.
Donut
Donut with a center label showing total / hovered slice value
Rounded Gap
Donut with rounded corners and a gap between slices
With Accessible Label
Accessible variant — announces label + description to screen readers on focus.
Drilldown
Click a slice — or Tab in and press Enter — to drill into it.
Big Slice
Slice radius encodes a SECOND measure (average minutes/day), proportional to `sqrt(minutes / maxMinutes)` so equal-AREA differences read as equal magnitude differences. "Deep work review" (90 min) renders at the chart's full outer radius; every other slice shrinks under it. Hover a slice to see both measures (count + minutes) in the center label.
Decorated
The pie at decoration 10 (#255). Every palette slice gains a series-pattern fill, so `PieChart` renders its `<pattern>` defs from a `.map()` — the path a missing list identity used to warn on. Pinned with `DecorationProvider` rather than a toolbar global so a headless run reaches the branch too.
Selection states
Grouped with outside labels
Eight channels folded to five + "Other" (`groupSmall={{ max: 5 }}`), with outside labels + leaders and a legend built from the same fold (`pieLegendItems`, RM-114). Resize the canvas: at `narrow` (< 480 px) the label layer's responsive default (`{ base: "outside", narrow: "none" }`) drops out and the legend — six rows, one per folded slice — carries the read instead.
Inside percent labels
Inside percentages: painted above `minAngle`, hidden below it (the "Fourpercent" slice — sweep ≈ 0.063 rad, well under the default 0.2).
Match-color labels
Outside labels painted in the slice's own colour (`matchColor: true`, #544) — routed through `seriesLabelInk` so the ink stays legible: measured live against `--chart-background` clears AA (≥4.5:1) in whatever theme the story runs under, not just the raw series colour (which fails 4.5:1 in every theme but one).
Half donut
The "election donut" preset — a 180° arc seated in data order (`sort` defaults to `"none"` once `half` is set), centre value below the arc.
Legend, hover only
Container legend (RM-118): `legend` mounts `ChartLegend` above the plot, one swatch per slice in data order. Hover only (R3) — hovering or focusing a row reuses Pie's own existing single-slice hover state, the same one a pointer hovering a slice already drives; there is no toggle/hide affordance for Pie yet (an `interactive: "toggle"` request downgrades to `"hover"`). At `narrow` the legend keeps its position but stacks one item per line.
Palette
`palette="sequential"` (RM-186): the slices take the sequential ramp, spread across its seven steps, instead of the twelve-colour categorical cycle.
API
PieChart
| Prop | Type | Description |
|---|---|---|
datarequired | PieData[] | Data array - each item represents a slice |
size | number | Chart size in pixels. If not provided, uses parent container size |
plotHeight | Responsive<ChartPlotHeight> | The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. |
innerRadius | number | Inner radius for donut charts, in px — or, below `1`, as a fraction of the outer radius (`0.6` keeps a responsive donut a donut at every size). Default: 0 (solid pie) |
plotAlign | "start" | "center" | Where the square plot sits in a container wider than it is tall: `"start"` (default, as before) or `"center"` — centred, and outside labels may run past the square into the free room either side instead of being clipped at it. |
align | "start" | "center" | Where the square plot sits in a container wider than it is tall. @deprecated Since 6.0.0, use `plotAlign`. Removed in 7.0.0. |
padAngle | number | Padding angle between slices in radians. Default: 0 |
cornerRadius | number | Corner radius for rounded slice edges. Default: 0 |
startAngle | number | Start angle in radians. Default: -PI/2 (top) |
endAngle | number | End angle in radians. Default: 3*PI/2 (full circle from top) |
className | string | Additional class name for the container |
hoveredIndex | number | null | Controlled hover state - index of hovered slice |
onHoverChange | (index: number | null) => void | Callback when hover state changes |
hoverOffset | number | Hover offset in pixels for slice hover effects. This also determines the padding around the chart to prevent clipping. Default: 10 |
childrenrequired | ReactNode | Child components (PieSlice, PieCenter, patterns, gradients, etc.) |
onDatapointClick | ChartDatapointClickHandler | Drill-down (#349). Fires when a slice is activated by pointer OR keyboard. Setting it mounts a keyboard-operable target layer OUTSIDE the aria-hidden SVG — one tab stop, arrow keys to traverse. Unset changes nothing. |
copyValueOnActivate | boolean | Put the datapoint's exact value on the clipboard when it is activated — the recovery path for a compact axis label. Default `false`; a chart with no interaction props still renders byte-identical DOM. A consumer-supplied `onDatapointClick` always wins. |
datapointLabel | ChartDatapointLabel | Override the accessible name of each keyboard drill-down target (#349). |
maxInteractiveDatapoints | number | Dev-warning threshold on the number of keyboard targets. Default 500 (#349). |
accessibleLabel | ChartA11yProps["accessibleLabel"] | Accessible name for the chart region (announces to AT on focus). |
accessibleDescription | ChartA11yProps["accessibleDescription"] | Supplemental description read by AT (e.g. slice names + value range). |
enterTransition | Transition | Framer Motion transition for slice enter animation |
enterStaggerScale | number | Scales slice stagger delays (1 = default). |
geometryScrubbing | boolean | High-frequency geometry updates (e.g. studio NumberField scrub). Uses plain SVG paths instead of Motion `d` / spring hover morphing. |
radiusKey | string | Second measure driving each slice's OUTER radius — angle × radius double encoding (lieflat G13 "Big Slice", #RM-030). Reads `data[i][radiusKey]` as a number; `outerRadius = innerRadius + (R - innerRadius) * sqrt(v / max)` (area-honest — equal AREA differences read as equal magnitude differences, not equal radius differences). A slice whose value is missing/non-numeric — or every slice, when `radiusKey` is unset — renders at the chart's full outer radius, today's behavior. |
referenceRings | number[] | Dashed reference rings (in `radiusKey`'s units) with small value labels — e.g. `[15, 30, 45]` for a "minutes/day" second measure. No-op when `radiusKey` is unset. |
seams | number | Paper-seam stroke (px) drawn between slices — the lieflat "3px paper seam" cut-paper look. Default: `0` (no seam — today's behavior). |
labels | PieChartLabelsConfig | Slice labels (RM-114, Datawrapper parity) — inside/outside placement, `label`/`value`/`percent` facts, leaders on the outside ring. Unset renders nothing extra (today's behavior). |
legend | ContainerLegendProp | Container legend (RM-118): one swatch per slice, mounted outside the plot via `useContainerLegend`. Unset renders nothing (R1) — today's behavior. Pie has no competing on-chart key the way `colorBy` (Bar) or RM-115's colour/shape key (Scatter) do, so R4's "one key per chart" never applies here. Hover-only (R3): hovering or focusing a legend item reuses Pie's own existing single-slice hover state — the SAME one a pointer hovering a slice already writes into (`hoveredIndex`/`onHoverChange` above) — so a legend hover and a pointer hover dim every other slice identically. An `interactive: "toggle"` request downgrades to `"hover"`: Pie has no hide-a-slice wiring yet (a hidden slice would silently change every other slice's percentage, which needs its own design pass — tracked as a follow-up, not built here). `{ values: true }` prints each slice's value (the folded "Other" slice's sum when `groupSmall` is on). |
groupSmall | PieGroupSmallOptions | Fold the smallest slices into one trailing "Other" slice (RM-114, `pie-grouping.ts`). **Setting this hands slice rendering to `PieChart` itself** — any `PieSlice` elements in `children` are ignored (their indices would no longer match the folded result); keep `PieCenter` and defs/gradient children, they are unaffected. Unset: today's behavior, `children` renders verbatim. |
sort | "desc" | "none" | Wedge order. `"desc"` — `startAngle` gets the LARGEST slice, then clockwise by descending size (d3-shape's own pie default). `"none"` (the default here) keeps `data`'s own order — pre-RM-114 behavior, kept as the default so an existing chart's wedge ANGLES never move under it (see the docblock on `effectiveSort` in `PieChartBase` for why the RM's stated `"desc"` default was not adopted). Slice INDEX identity never changes either way — a `<PieSlice index={i}>` always targets `data[i]`; only the angular position can move. |
half | boolean | The "election donut" preset (RM-114): `startAngle: -π/2, endAngle: π/2` — a dome across the top half, seated in data order by default (`sort` unset here behaves exactly as elsewhere: `"none"`). The centre slot (a `PieCenter` child) is positioned BELOW the arc instead of in the chart's geometric middle. Overrides any `startAngle`/`endAngle` props. Default `false`. |
palette | ChartPalette | Colour ramp for the slices (RM-186), through `resolvePalette`; an item's own `color` still wins. Unset: the twelve categorical colours, cycled, as before. |
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. |
margin | number | Partial<Margin> | Space around the plot, in pixels: one number for every side, or per side. |
status | ChartStatus | Show the loading skeleton until the data is ready. |
empty | ChartEmptyState | Title and message shown when there is nothing to plot. |
valueFormat | ChartValueFormat | How values are printed: a preset, or a spec for decimals, sign, prefix and suffix. |
locale | string | BCP 47 locale the numbers are formatted in. |
currency | string | ISO 4217 currency code for currency values. |
maxFractionDigits | number | Most digits printed after the decimal point. |
PieCenter
| Prop | Type | Description |
|---|---|---|
defaultLabel | string | Label shown below the value. Default: "Total" when not hovering |
formatOptions | ChartStatFlowFormat | Format options for NumberFlow. Default: standard notation |
children | (props: { value: number; label: string; isHovered: boolean; data: { label: string; value: number; color?: string; fill?: string }; }) => ReactNode | Custom render function for complete control over center content |
className | string | Additional class name for the container |
valueClassName | string | Class name for the value text. Scales with center size via container queries. |
labelClassName | string | Class name for the label text. Scales with center size via container queries. |
prefix | string | Prefix to show before the number (e.g., "$") |
suffix | string | Suffix to show after the number (e.g., "%") |
PieCenterShell
| Prop | Type | Description |
|---|---|---|
centerValuerequired | number | Value shown with NumberFlow (same role as pie total when not hovering) |
contextSizerequired | number | Square reference size for pie context (matches `PieChart` `size`) |
innerRadiusPxrequired | number | Inner radius in px — must be > 0 so `PieCenter` renders |
animateEntrance | boolean | When true (default), the first paint uses `0` then updates to `centerValue` on the next frame so NumberFlow can run an entrance transition. Subsequent `centerValue` updates animate as usual. |
defaultLabel | string | Label shown below the value. Default: "Total" when not hovering |
formatOptions | ChartStatFlowFormat | Format options for NumberFlow. Default: standard notation |
className | string | Additional class name for the container |
valueClassName | string | Class name for the value text. Scales with center size via container queries. |
labelClassName | string | Class name for the label text. Scales with center size via container queries. |
prefix | string | Prefix to show before the number (e.g., "$") |
suffix | string | Suffix to show after the number (e.g., "%") |
PieLabels
| Prop | Type | Description |
|---|---|---|
configrequired | PieLabelsConfig | |
arcsrequired | readonly PieArcData[] | |
centerrequired | number | |
innerRadiusrequired | number | |
outerRadiusrequired | number | |
getColorrequired | (index: number) => string | |
textForrequired | (index: number) => PieLabelTextParts | `(index) => label/value/percent text` for the fields `config.show` asks for. |
PieSlice
| Prop | Type | Description |
|---|---|---|
indexrequired | number | Index of the slice in the data array |
color | string | Optional color override - falls back to data color or palette |
fill | string | Optional fill override for patterns/gradients (e.g., "url(#patternId)") |
animate | boolean | Animate the slice on mount. Default: true |
showGlow | boolean | Show glow effect on hover. Default: true |
hoverEffect | PieSliceHoverEffect | Hover effect type. Default: "translate" - "translate": Slice moves outward along its radial axis - "grow": Slice extends its outer radius (gets longer) - "none": No hover animation |
hoverOffset | number | Distance in pixels for hover effect (translate distance or grow amount). Defaults to PieChart's hoverOffset |
className | string | Additional CSS class |
outerRadiusOverride | number | Per-slice outer radius override (px) — RM-030's `radiusKey`. Injected automatically by `PieChart` via `cloneElement` when its `radiusKey` prop is set; a `PieSlice` rendered standalone (or inside a chart with no `radiusKey`) never receives it and falls back to the chart's uniform `outerRadius` — today's behavior. |
seams | number | Paper-seam stroke (px) between this slice and its neighbors — RM-030's `seams`. Injected automatically by `PieChart` via `cloneElement` when its `seams` prop is > 0. Default: no stroke (today's behavior). |
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| series | --chart-1 … --chart-5 |