Skip to main content

FunnelChart

Part to Wholechart

Stage-by-stage drop-off through an ordered pipeline.

import { FunnelChart } 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 FunnelChart 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 FunnelChart up before writing code: `npx -y @elabs-ai/components-cli docs FunnelChart` 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? Stage-to-stage drop-off.
  • a sequential process with drop-off between stages

Avoid

  • the stages are not sequential, or there is no drop-off story to tell
  • Using it for unordered categories — a funnel asserts a sequence; unordered data belongs in a bar chart.
  • Encoding stage identity by colour alone — label each stage; colour is not a legend.
  • Hiding the absolute counts behind percentages only — a funnel without its base number cannot be judged.

Works with

Pairs with

Examples

11 live examples

Default

Loading

Loading skeleton (RM-183) — shown while `status="loading"`, sized like the real chart.

draws once loading finishes

Regression lock (RM-183 review) — the ResizeObserver that measures the plot never attached while `status="loading"` hid the real chart node, so flipping to "ready" left the funnel blank until an unrelated resize fired. This exercises that exact transition end to end in a real browser.

Vertical

With Grid

Straight Edges

With Accessible Label

Accessible variant — announces label + description to screen readers on focus.

Token Colors

Conversion — between stages

Stage-to-stage conversion (lieflat L13) at every boundary — the number funnel readers actually want, distinct from `showPercentage`'s "% of the first stage" badge. Reconciles with the data: 4,800/12,000 = 40%, 2,100/4,800 = 44%, 840/2,100 = 40%.

Conversion — margin

Same conversion numbers, stacked in the left margin (lieflat L13's "62% GET THROUGH").

Drilldown

Click a stage — or Tab in and press Enter — to drill into it.

API

FunnelChart

PropTypeDescription
datarequiredFunnelStage[]
orientation"horizontal" | "vertical"
colorstring
layersnumber
classNamestring
styleCSSProperties
plotHeightResponsive<ChartPlotHeight>The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint.
showPercentageboolean
showValuesboolean@deprecated Use `labels` — `true`/`false` keep meaning the same thing (ADR 0042 A.3, row 13). Read until 7.0.0, with one development warning; when both are set, `labels` wins.
labelsboolean | ChartDataLabelsConfigPrint each stage's value beside it. Default `true`.
showLabelsboolean
hoveredIndexnumber | nullControlled hover state — index of the hovered segment
onHoverChange(index: number | null) => voidCallback when hover state changes
formatPercentage(pct: number) => string
formatValue(value: number) => string
staggerDelaynumberStagger delay between segments in seconds. Default 0.12
enterTransitionTransitionFramer Motion transition for segment enter animation
gapnumberGap between segments in pixels. Default 4
renderPattern(id: string, color: string) => ReactNodeRender a visx pattern definition. Receives a unique `id` string per segment and the resolved `color`. Return a `<PatternLines>` (or any visx pattern) inside an SVG `<defs>`. The component will use `fill="url(#id)"` on the innermost ring while keeping outer halo rings as solid color.
onDatapointClickChartDatapointClickHandlerDrill-down (#349). Fires when a stage 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. stage names + values).
legendContainerLegendPropContainer legend (#610, the RM-118 engine): mounts one key outside the plot, in the shared legend look every other chart uses. A funnel draws ONE measure — its stages are already labelled on the plot — so the key names that measure: one swatch in the chart's `color`, labelled `seriesLabel`. Static (`interactive` caps at `"none"`): with one measure there is no other series to dim or hide. Unset renders nothing (today's behaviour); a truthy `legend` with no `seriesLabel` also renders nothing. `{ values: true }` prints the first stage's value (the funnel's 100%), through `formatValue`.
seriesLabelstringThe measure's display name — the legend's one entry (see `legend`).
edges"curved" | "straight"Edge style for the funnel segments. Default "curved"
labelLayout"spread" | "grouped"Controls how segment labels (value, percentage, stage name) are arranged. - "spread": Value/percentage/label are spread apart (top/center/bottom for horizontal, left/center/right for vertical). This is the default. - "grouped": All label items stack together in a tight group. When "grouped", use `labelOrientation` and `labelAlign` for full control.
labelOrientation"vertical" | "horizontal"Stack direction of the label group. Only applies when `labelLayout="grouped"`. - "vertical": Items stack top-to-bottom. Default for horizontal funnels. - "horizontal": Items stack left-to-right. Default for vertical funnels.
labelAlign"center" | "start" | "end"Where the label group sits within the segment cell. - "center" (default), "start", "end" For horizontal funnel: start=top, end=bottom. For vertical funnel: start=left, end=right.
showConversion"between" | "margin" | falseStage-to-stage conversion — the % of the PREVIOUS stage's value, rendered as a small opaque plate (lieflat L13's "62% GET THROUGH" margin note — the number funnel readers actually want, distinct from the existing `showPercentage` badge which is always "% of the first stage"). The plate carries its own ground/ink pair (like `SegmentLabel`'s percentage pill), so it stays legible on any band fill in any theme — see #239. - `"between"` places one annotation at the boundary between each pair of adjacent segments. - `"margin"` stacks all transitions in a column near the funnel's leading edge (left edge for horizontal, top edge for vertical). - `false` (default) renders nothing — an existing `FunnelChart` with no `showConversion` prop is unaffected. When set, the interactive stage overlay (hover/click target) also gets a native `title` tooltip reading "N% of previous stage · M% of first stage".
paletteChartPaletteColour ramp for the stages (RM-186): the chart-level colour becomes the palette's first colour unless `color` is set; a stage's own `color` still wins. Unset: `--chart-1`, as before.
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: Pick<FrameSizeGroupProps, "margin">, Pick<ChartStateGroupProps, "status" | "empty">, Pick<ValueFormatGroupProps, "valueFormat" | "locale" | "currency" | "maxFractionDigits">