Skip to main content

PieChart

Part to Wholechart

Part-of-whole split across a handful of categories.

Pie chart, 4 slices; largest: Ledger at 38 (38%)
import { PieChart } 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 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

  • PieSlice
  • PieCenter
  • PieProvider

Examples

15 live 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

PropTypeDescription
datarequiredPieData[]Data array - each item represents a slice
sizenumberChart size in pixels. If not provided, uses parent container size
plotHeightResponsive<ChartPlotHeight>The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint.
innerRadiusnumberInner 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.
padAnglenumberPadding angle between slices in radians. Default: 0
cornerRadiusnumberCorner radius for rounded slice edges. Default: 0
startAnglenumberStart angle in radians. Default: -PI/2 (top)
endAnglenumberEnd angle in radians. Default: 3*PI/2 (full circle from top)
classNamestringAdditional class name for the container
hoveredIndexnumber | nullControlled hover state - index of hovered slice
onHoverChange(index: number | null) => voidCallback when hover state changes
hoverOffsetnumberHover offset in pixels for slice hover effects. This also determines the padding around the chart to prevent clipping. Default: 10
childrenrequiredReactNodeChild components (PieSlice, PieCenter, patterns, gradients, etc.)
onDatapointClickChartDatapointClickHandlerDrill-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.
copyValueOnActivatebooleanPut 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.
datapointLabelChartDatapointLabelOverride the accessible name of each keyboard drill-down target (#349).
maxInteractiveDatapointsnumberDev-warning threshold on the number of keyboard targets. Default 500 (#349).
accessibleLabelChartA11yProps["accessibleLabel"]Accessible name for the chart region (announces to AT on focus).
accessibleDescriptionChartA11yProps["accessibleDescription"]Supplemental description read by AT (e.g. slice names + value range).
enterTransitionTransitionFramer Motion transition for slice enter animation
enterStaggerScalenumberScales slice stagger delays (1 = default).
geometryScrubbingbooleanHigh-frequency geometry updates (e.g. studio NumberField scrub). Uses plain SVG paths instead of Motion `d` / spring hover morphing.
radiusKeystringSecond 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.
referenceRingsnumber[]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.
seamsnumberPaper-seam stroke (px) drawn between slices — the lieflat "3px paper seam" cut-paper look. Default: `0` (no seam — today's behavior).
labelsPieChartLabelsConfigSlice labels (RM-114, Datawrapper parity) — inside/outside placement, `label`/`value`/`percent` facts, leaders on the outside ring. Unset renders nothing extra (today's behavior).
legendContainerLegendPropContainer 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).
groupSmallPieGroupSmallOptionsFold 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.
halfbooleanThe "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`.
paletteChartPaletteColour ramp for the slices (RM-186), through `resolvePalette`; an item's own `color` still wins. Unset: the twelve categorical colours, cycled, as before.
selectionStatesChartSelectionStatesResolver<Record<string, unknown>>Maps a mark (category, optional series key, optional raw row) to its selection state. Unset → the chart renders exactly as before.
dimExcludedbooleanDim `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.
marginnumber | Partial<Margin>Space around the plot, in pixels: one number for every side, or per side.
statusChartStatusShow the loading skeleton until the data is ready.
emptyChartEmptyStateTitle and message shown when there is nothing to plot.
valueFormatChartValueFormatHow values are printed: a preset, or a spec for decimals, sign, prefix and suffix.
localestringBCP 47 locale the numbers are formatted in.
currencystringISO 4217 currency code for currency values.
maxFractionDigitsnumberMost digits printed after the decimal point.

Also accepts: ChartSelectionProps, Pick<FrameSizeGroupProps, "margin">, Pick<ChartStateGroupProps, "status" | "empty">, Pick<ValueFormatGroupProps, "valueFormat" | "locale" | "currency" | "maxFractionDigits">

PieCenter

PropTypeDescription
defaultLabelstringLabel shown below the value. Default: "Total" when not hovering
formatOptionsChartStatFlowFormatFormat options for NumberFlow. Default: standard notation
children(props: { value: number; label: string; isHovered: boolean; data: { label: string; value: number; color?: string; fill?: string }; }) => ReactNodeCustom render function for complete control over center content
classNamestringAdditional class name for the container
valueClassNamestringClass name for the value text. Scales with center size via container queries.
labelClassNamestringClass name for the label text. Scales with center size via container queries.
prefixstringPrefix to show before the number (e.g., "$")
suffixstringSuffix to show after the number (e.g., "%")

PieCenterShell

PropTypeDescription
centerValuerequirednumberValue shown with NumberFlow (same role as pie total when not hovering)
contextSizerequirednumberSquare reference size for pie context (matches `PieChart` `size`)
innerRadiusPxrequirednumberInner radius in px — must be > 0 so `PieCenter` renders
animateEntrancebooleanWhen 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.
defaultLabelstringLabel shown below the value. Default: "Total" when not hovering
formatOptionsChartStatFlowFormatFormat options for NumberFlow. Default: standard notation
classNamestringAdditional class name for the container
valueClassNamestringClass name for the value text. Scales with center size via container queries.
labelClassNamestringClass name for the label text. Scales with center size via container queries.
prefixstringPrefix to show before the number (e.g., "$")
suffixstringSuffix to show after the number (e.g., "%")

Also accepts: Omit<PieCenterProps, "children">

PieLabels

PropTypeDescription
configrequiredPieLabelsConfig
arcsrequiredreadonly PieArcData[]
centerrequirednumber
innerRadiusrequirednumber
outerRadiusrequirednumber
getColorrequired(index: number) => string
textForrequired(index: number) => PieLabelTextParts`(index) => label/value/percent text` for the fields `config.show` asks for.

PieSlice

PropTypeDescription
indexrequirednumberIndex of the slice in the data array
colorstringOptional color override - falls back to data color or palette
fillstringOptional fill override for patterns/gradients (e.g., "url(#patternId)")
animatebooleanAnimate the slice on mount. Default: true
showGlowbooleanShow glow effect on hover. Default: true
hoverEffectPieSliceHoverEffectHover effect type. Default: "translate" - "translate": Slice moves outward along its radial axis - "grow": Slice extends its outer radius (gets longer) - "none": No hover animation
hoverOffsetnumberDistance in pixels for hover effect (translate distance or grow amount). Defaults to PieChart's hoverOffset
classNamestringAdditional CSS class
outerRadiusOverridenumberPer-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.
seamsnumberPaper-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.

StateToken
series--chart-1 … --chart-5