import { FunnelChart } 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 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
Used inside
Pairs with
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
| Prop | Type | Description |
|---|---|---|
datarequired | FunnelStage[] | |
orientation | "horizontal" | "vertical" | |
color | string | |
layers | number | |
className | string | |
style | CSSProperties | |
plotHeight | Responsive<ChartPlotHeight> | The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. |
showPercentage | boolean | |
showValues | boolean | @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. |
labels | boolean | ChartDataLabelsConfig | Print each stage's value beside it. Default `true`. |
showLabels | boolean | |
hoveredIndex | number | null | Controlled hover state — index of the hovered segment |
onHoverChange | (index: number | null) => void | Callback when hover state changes |
formatPercentage | (pct: number) => string | |
formatValue | (value: number) => string | |
staggerDelay | number | Stagger delay between segments in seconds. Default 0.12 |
enterTransition | Transition | Framer Motion transition for segment enter animation |
gap | number | Gap between segments in pixels. Default 4 |
renderPattern | (id: string, color: string) => ReactNode | Render 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. |
onDatapointClick | ChartDatapointClickHandler | Drill-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. |
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. stage names + values). |
legend | ContainerLegendProp | Container 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`. |
seriesLabel | string | The 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" | false | Stage-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". |
palette | ChartPalette | Colour 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. |
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. |