FunnelChart shows a sequence of monotonically-shrinking stages and makes the drop-off at each stage the visual punchline. It's deceptively simple: five trapezoids stacked vertically, with a trapezoidal connector between each pair. It survives because the metaphor is universally legible: each stage is a sieve, and you can see at a glance which sieve is throwing the most stuff away.
Why this exists
A bar chart of stage counts gets you most of the way there. Sorted descending, it even gets the monotonic property right. But charts aren't just about the most effective way to communicate data, they are also about the presenting data in a way that is compelling and which audiences expect. Audiences expect graphical funnels.
Common applications:
- Marketing / sales funnels. Visited → signed up → trial → paid → retained. Where the most dropoff occurs tells the growth team where to invest.
- Conversion analysis. Step-by-step checkout flow, onboarding completion, form submission.
- Recruiting pipelines. Applied → phone screen → onsite → offer → accepted.
- Manufacturing / QA yields. Raw → assembled → tested → packed → shipped.
Live demo
A SaaS signup funnel for a hypothetical product. The steepest connectors point to where the team should focus.
Visited → Signed up loses 62% is mostly the landing page's job. Signed up → Activated loses 39% is onboarding's job. Activated → Subscribed loses 63% which is pricing/value-prop's job. Subscribed → Retained loses 22% which is product's job.
Vertical vs horizontal
FunnelChart ships in two orientations and they're not interchangeable. The default isorientation="horizontal" (the chart above). The same data withorientation="vertical" below puts the stages across the x-axis and bars run downward from a baseline:
Which one to pick comes down to three questions:
- Stage labels. Long labels ("Email verification confirmed in <5 min") read cleanly on the horizontal layout. The label sits flush against its bar with room to breathe. The vertical layout forces labels under each bar and they wrap or angle.
- Number of stages. Horizontal handles 7–10 stages without crowding; the chart just gets taller. Vertical works up to ~6 stages before the bars get too thin to read.
- Narrative direction. Horizontal feels like "down the funnel" (top → bottom is the literal metaphor). Vertical feels like a timeline ("over time, stages drop off") and pairs well with sequential conversion across a flow that has time in the x-axis.
Default to horizontal. Switch to vertical when stages are few and the chart shares an x-axis with neighboring time series (e.g. a dashboard where the funnel sits next to a weekly line chart).
How to read it
- Trapezoid width at each step encodes the count at that step.
- Connector slope between adjacent steps encodes the drop-off so steep means a leaky stage.
- Connector opacity is configurable via
connectorOpacity. Keep low when the point is the stages, raise when the point is the gaps.
When to reach for it
Reach for FunnelChart when:
- Stages are ordered and counts are monotonically non-increasing. (If the second stage is bigger than the first, you have a different shape and FunnelChart will look wrong.)
- You want stage-to-stage drop-off to be the visual headline.
- The audience knows what "a funnel" means. (They almost certainly do.)
Reach for something else when:
- Counts don't shrink monotonically then you should try aBarChart orSwimlaneChart ordered by step.
- You want to show conversion rates, not counts. Consider a stacked bar chart with category="converted/ dropped" per step. The funnel metaphor doesn't show rates intuitively (it shows magnitudes that the eye translates into rates).
- You have multiple cohorts you want to compare across the same funnel: small-multiple FunnelCharts work, but a GroupedBarChart per step might be easier to read.
Wiring it up
import { FunnelChart } from "semiotic" <FunnelChart data={[ { step: "Visited", count: 25000 }, { step: "Signed up", count: 9400 }, { step: "Activated", count: 5700 }, { step: "Subscribed", count: 2100 }, { step: "Retained", count: 1640 }, ]} stepAccessor="step" valueAccessor="count" />Minimum required props are data, stepAccessor, andvalueAccessor. Add categoryAccessor (optional) for per-category coloring across the same funnel. Tooltips, legends, annotations, and themes all work the same way as the rest of the chart family.
Streaming / push mode
Funnels are the classic dashboard chart, and dashboards are the classic place where data updates in real time. Marketing watches today's signup numbers grow through the day, ops watches the orders-shipped count tick up as warehouses log scans. Push mode lets the funnel update live without any re-render dance.
The demo below pushes one stage at a time, the way the chart might fill in as a daily extract job finishes each stage's count:
Wiring:
const ref = useRef() // Every time a stage count finishes computing ref.current.push({ step: "Subscribed", count: 2100 }) // Counts that change over time go through update() ref.current.update("Subscribed", (d) => ({ ...d, count: 2104 })) <FunnelChart ref={ref} stepAccessor="step" valueAccessor="count" dataIdAccessor="step" // required for update() / remove() />Why push mode helps: bar-and-trapezoid charts animate size transitions naturally. Using a tick that bumps a stage's count from 2100 to 2104 reads as the bar growing slightly. Withdata resets, the chart redraws from scratch and the animation is lost. Push triggers the chart's transition path, so the size delta is the animated motion the viewer actually wants to see.