DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
ChartArea ChartBar ChartLine ChartLive Line ChartComposed ChartPie ChartFunnel ChartRadar ChartRadial ChartSankey ChartScatter ChartCandlestick ChartSunburst ChartChoropleth ChartActivity Heatmap
AI agentsContributing
  1. Components
  2. Funnel Chart

Funnel Chart

Token-driven funnel chart for ordered pipeline stages, built on Recharts v3 — stacked or sideways, centred or from a shared edge, with the conversion rate from the first stage and from the previous one printed beside the shape, a staggered entrance and a named accessible figure.

$ pnpm add @tessinaui/ui

When to use

A funnel answers one question: how many make it from one stage to the next, and where are they lost?

  • Ordered stages of one process. Visitors, sign-ups, trials, paid. The order is the point: every stage is a subset of the one before it, or was meant to be.
  • Three to about seven stages. Fewer than three is a single rate, which reads better as a number. Past seven the last stages are too thin to tell apart: group the early steps or split the funnel in two.
  • When both rates matter. The share of the first stage says how much of the whole is left; the rate from the previous stage says where the loss happens. The chart prints both, each in its own place.
  • Beside a headline number. A card whose title is the end-to-end rate and whose body is the funnel shows the result and its cause together.

Print the count and the rate, always. A funnel's shape is a picture of the numbers, not a replacement for them: centred bars share no edge, so two stages of similar size cannot be compared by eye. Where exact comparison matters, grow the stages from a shared edge with align="start".

When not to use

  • Categories with no order. Use BarChart.
  • Change over time. A funnel is one period. To follow a conversion rate across weeks use LineChart.
  • Parts of a whole that do not flow into each other. Use PieChart.
  • One rate. Use Meter or a number.
  • Paths that branch. A funnel has one way through. Steps people take in any order, or leave and rejoin, need a flow diagram.
  • Stages as navigation. A row of stage tabs with counts is Tabs or Stepper, not a chart.

Three things to avoid when you do use one:

  • Do not truncate a stage name. A stacked funnel gives every name a full line; a sideways one does not. With long names or a narrow column, keep the default orientation.
  • Do not add a legend or a value axis. Every stage is named beside its bar and every value is printed; a key or a scale repeats them.
  • Do not colour each stage differently. Stage order already carries identity. A second colour is a second meaning, and it has to be named.

Installation

Terminal
pnpm add @tessinaui/ui recharts

Usage

example.tsx
import { FunnelChart } from "@tessinaui/ui";

const data = [
  { stage: "Visitors", people: 18240 },
  { stage: "Sign-ups", people: 4960 },
  { stage: "Trials", people: 1120 },
  { stage: "Paid", people: 386 },
];

<FunnelChart
  data={data}
  dataKey="people"
  nameKey="stage"
  label="One visitor in fifty becomes a paying customer"
/>

Rows are the stages, in funnel order. config is optional: pass it to give a stage a display label, or a colour of its own.

example.tsx
import type { ChartConfig } from "@tessinaui/ui";

// config is keyed by each stage's nameKey value
const config: ChartConfig = {
  signups: { label: "Sign-ups" },
  paid: { label: "Paid", color: "var(--chart-2)" },
};

Examples

Default

Stages stack top to bottom, centred, in tones of one colour that run from the strongest at the top. The number beside each stage is its value and its share of the first stage; the number between two stages is the rate from one to the next. The stages draw in one after another, in funnel order.

Horizontal

orientation names the direction the funnel flows. "horizontal" runs the stages along the inline axis, with each value over its stage and each name under it. It needs room: on a phone, or with long stage names, keep the default.

From a shared edge

align="start" grows every stage from the same edge, so lengths compare exactly. showTrack draws a rail the length of the largest stage behind each bar: what is left of the rail is what was lost.

Conversion rates

conversion chooses which rate prints: "first", "previous", "both" (default) or "none". The share of the first stage always sits beside the stage; the rate from the previous stage always sits between the two stages it measures, so the two are never confused.

Formatting and labels

valueFormatter and percentFormatter feed the labels, the tooltip and the summary assistive technology reads. With showLabels={false} the tooltip carries every number.

Corner rounding

Connector

A wash of each stage's colour carries it into the next. curve="linear" straightens its edges, showConnector={false} removes it, and stageGap and maxStageSize set how much of each stage's pitch the bar takes.

Uneven data

A stage larger than the one before it is drawn longer and prints its real rate. A stage at zero keeps its place and prints 0. A stage that is not measured is null: it prints a dash, and no rate is computed through it.

A second colour

A color in config, or a stageColor callback, replaces a stage's tone. Use it for a meaning the description names.

In a narrow card

In a column too narrow for the shape and both label columns, the share of the first stage moves to the tooltip and the value stays. A stage name that does not fit its column wraps onto a second line.

Right to left

dir="rtl" mirrors the layout: stage names move to the right, values and rates to the left, and a sideways funnel runs right to left. An explicit dir also switches the tooltip and summary strings to Arabic; strings replaces them for any other language.

Accessibility

On a coloured plate

States

API Reference

FunnelChart

PropTypeDefaultDescription
dataFunnelChartDatum[]—One row per stage, in funnel order.
dataKeystring—Datum key holding each stage's number. null or a missing value is "not tracked".
nameKeystring—Datum key holding the stage name.
configChartConfig—Optional per-stage label, icon, and color or theme, keyed by the stage's name.
labelstring—Accessible name; renders the chart as a named figure. State the insight.
descriptionstring—Visually hidden summary. A sentence per stage is always appended.
orientation"vertical" | "horizontal""vertical"The direction the funnel flows. Vertical stacks stages top to bottom.
align"center" | "start""center"Centre the stages, or grow them from a shared edge.
size"sm" | "md" | "lg""md"Vertical: the pitch of one stage, 40 / 48 / 56px, so height follows the stage count. Horizontal: the aspect ratio, 3:1 / 2:1 / 16:9.
rounded"none" | "sm" | "md" | "lg" | "full""md"Corner rounding of each stage.
conversion"both" | "first" | "previous" | "none""both"Which conversion rates print.
showLabelsbooleantruePrint each stage's value and rates.
showCategoryAxisbooleantruePrint the stage names.
showTooltipbooleantrueRender the tooltip.
showConnectorbooleantrueJoin each stage to the next with a wash of its colour.
curve"monotone" | "linear""monotone"The connector's edge.
showTrackbooleanfalseA rail the length of the largest stage behind each bar. With align="start".
stageGapnumber | string"40%"Space between two stages: px, or a percentage of one stage's pitch.
maxStageSizenumber—Cap on a stage's thickness, in px.
valueFormatter(value, name) => string | number—Formats values in labels, the tooltip and the summary.
percentFormatter(ratio, basis) => stringwhole percents, one decimal under 10%Formats rates. basis is "first" or "previous".
stageColor(datum, index, name) => string | undefined—Per-stage fill override.
stringsPartial<FunnelChartStrings>—Replaces single entries of the built-in strings.
animatebooleantrueDraw the stages in one after another. Reduced motion turns it off regardless.
dir"ltr" | "rtl"—Direction; unset by default so a global dir is respected.
loadingbooleanfalseRender the skeleton.
errorReactNode—Render the error message instead of the chart.
emptyStateReactNode"No data to display"Shown with no rows, or when every stage is zero or untracked.
childrenReactNode—Extra Recharts elements drawn inside the plot.

Data attributes

AttributeOnValue
data-componentroot"funnel-chart"
data-slotroot"chart" (from ChartContainer)
data-orientationroot"vertical" or "horizontal"
data-alignroot"center" or "start"
data-slotconnector group, connector"funnel-chart-connectors", "funnel-chart-connector"
data-slotlabel group"funnel-chart-labels"
data-slotvalue, share, rate text"funnel-chart-value", "funnel-chart-share", "funnel-chart-step"

Accessibility

  • label is the accessible name. It renders the chart as a named role="figure". Without it the keyboard surface inside is an unnamed application. State the insight ("Half of all leads drop before a demo"), not the shape.
  • A static read needs no interaction. The chart writes a visually hidden sentence per stage ("Leads: 6,800 (55% of Visitors)") and appends it to description, through the same formatters the labels use.
  • The drawn labels are hidden from assistive technology, because that summary already reads every one of them.
  • Arrow keys step through the stages. Down and Up in a stacked funnel, the inline arrows in a sideways one, mirrored in a right-to-left layout. Escape dismisses the tooltip without moving focus.
  • Nothing is hover-only. Pointing at a stage, tapping it and stepping to it with the keyboard mark it the same way and open the same tooltip.
  • Colour is never the only encoding (WCAG 1.4.1). Position, length and the printed numbers carry every reading; the tones only reinforce order.
  • Offer the numbers as a table wherever the funnel is the main content, as the accessibility example does.
  • Every tone of the default ramp is at least 3:1 against the background in both themes; value and rate text is at least 4.5:1.

Screen-reader passes have not been recorded for this component yet.

Agent notes

  • Rows are stages, in order. The chart does not sort: the first row is the top of the funnel and the base of every "of first" rate.
  • orientation names the flow, not the bars. "vertical" (default) is the stacked funnel, whose bars lie sideways.
  • null is "not tracked"; 0 is a count of zero. They print and compute differently, so do not coerce a missing value to 0.
  • A rate that cannot be computed is not printed: anything divided by a zero or untracked stage. The chart never shows NaN or ∞.
  • Every stage at zero or untracked renders the empty state.
  • A stacked funnel sets its own height from the stage count and size. To size it yourself pass a height class in className.
  • showTrack needs align="start"; a centred stage has no rail to fill.
  • The rate between two stages is hidden when the gap cannot hold it (a sideways funnel in a narrow column). It stays in the tooltip and the summary.
  • Default number text is English-formatted (12,400, 55%) so the server and the browser render the same string. Pass valueFormatter and percentFormatter to localise.
  • config keys are stage names, not series: one funnel is one measure.

Theming

A stage with no colour of its own takes a tone of --chart-1: the ramp steps evenly across the band of tones that stay at least 3:1 against the background, strongest at the top of the funnel in both themes. A color or theme in config, or stageColor, replaces a stage's tone. The connector is a 25% wash of the stage above it. Stage names use --muted-foreground; values and the rate between stages use --foreground; the share of the first stage uses --muted-foreground. On a coloured plate every stage takes --surface-ink in one tone, and the texts take that ink and its muted tier. Corners follow rounded on the library's radius scale.

Pie ChartRadar Chart

On this page

When to useWhen not to useInstallationUsageExamplesDefaultHorizontalFrom a shared edgeConversion ratesFormatting and labelsCorner roundingConnectorUneven dataA second colourIn a narrow cardRight to leftAccessibilityOn a coloured plateStatesAPI ReferenceFunnelChartData attributesAccessibilityAgent notesTheming