FunnelChart
Steps in order, as bars of each step's share of the first, with the counts and the share of the step before as text. Drawn on the server, with no client JavaScript.
Import
import { FunnelChart } from '@nizivo/charts/funnel-chart';Static or interactive
Server. FunnelChart has no 'use client' and renders no island: every value is already text in
its rows, so there is nothing to rove. The rows are hidden from assistive technology, which reads the
data table instead (step, count and % of previous), as Figma's description asks; tableView="details"
shows the table to everyone. How to choose.
Examples
Basic
| Step | Sessions | % of previous |
|---|---|---|
| Sessions | 48,210 | — |
| Added to cart | 9,642 | 20.0% |
| Reached checkout | 4,628 | 48.0% |
| Purchased | 2,914 | 63.0% |
import type { JSX } from 'react';
import { FunnelChart } from '@nizivo/charts/funnel-chart';
interface Step {
name: string;
sessions: number;
}
// Figma 03's funnel: a store's checkout, sessions to purchases.
const CHECKOUT: Step[] = [
{ name: 'Sessions', sessions: 48210 },
{ name: 'Added to cart', sessions: 9642 },
{ name: 'Reached checkout', sessions: 4628 },
{ name: 'Purchased', sessions: 2914 },
];
export function FunnelChartExample(): JSX.Element {
return (
<FunnelChart
title="Checkout funnel"
summary="Sessions to purchases, October"
data={CHECKOUT}
step={(d) => d.name}
value={(d) => d.sessions}
labels={{ value: 'Sessions' }}
/>
);
}
Locale
Formatted for Germany, with the values open under the funnel.
Werte
| Schritt | Besuche | % vom vorigen |
|---|---|---|
| Besuche | 12.840 | — |
| Warenkorb | 3.210 | 25,0 % |
| Kasse | 1.765 | 55,0 % |
| Gekauft | 1.102 | 62,4 % |
import type { JSX } from 'react';
import { FunnelChart } from '@nizivo/charts/funnel-chart';
export function FunnelChartLocaleExample(): JSX.Element {
return (
<FunnelChart
title="Bestellprozess"
data={[
{ name: 'Besuche', sessions: 12840 },
{ name: 'Warenkorb', sessions: 3210 },
{ name: 'Kasse', sessions: 1765 },
{ name: 'Gekauft', sessions: 1102 },
]}
step={(d) => d.name}
value={(d) => d.sessions}
locale="de-DE"
tableView="details"
labels={{ step: 'Schritt', value: 'Besuche', share: '% vom vorigen', table: 'Werte' }}
/>
);
}
Status
Loading, empty and failed: the same box, Figma's four-step height.
import type { JSX } from 'react';
import { FunnelChart } from '@nizivo/charts/funnel-chart';
interface Step {
name: string;
sessions: number;
}
// Figma 03's funnel: a store's checkout, sessions to purchases.
const CHECKOUT: Step[] = [
{ name: 'Sessions', sessions: 48210 },
{ name: 'Added to cart', sessions: 9642 },
{ name: 'Reached checkout', sessions: 4628 },
{ name: 'Purchased', sessions: 2914 },
];
export function FunnelChartStatusExample(): JSX.Element {
return (
<div>
<FunnelChart
title="Checkout funnel"
status="loading"
data={[]}
step={(d: Step) => d.name}
value={(d) => d.sessions}
/>
<FunnelChart
title="Checkout funnel"
data={[]}
step={(d: Step) => d.name}
value={(d) => d.sessions}
/>
<FunnelChart
title="Checkout funnel"
status="error"
data={CHECKOUT}
step={(d) => d.name}
value={(d) => d.sessions}
/>
</div>
);
}
Steps
funnelSteps(items) is the rule FunnelChart applies, exported so a server can compute the same
numbers:
- Steps keep their input order. A value that is not a finite number, or is below zero, counts as 0.
- A bar is as long as the step's value over the first step's, clamped to 0–1: a later step above the first draws a full bar, and a first step of 0 draws none.
- The share is the step's value over the previous step's. The first step has none, nor does a step
after a step of 0;
labels.noShare("—") stands in for it.
Props
Server component. Props: FunnelChartProps<T>. Source: src/funnel-chart/funnel-chart.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
datarequired | readonly T[] | – | One datum per step, in funnel order. |
empty | ReactNode | – | Replaces the default empty message. From |
error | ReactNode | – | Replaces the default error message, for example an InlineError with a retry action. From |
format | Intl.NumberFormatOptions | Intl.NumberFormat's own ({}) | Counts. |
labels | Partial<FunnelChartLabels> | – | |
locale | string | 'en-US' | BCP 47 locale every number is formatted in, once, on the server (§16.4). |
pending | boolean | – | A refresh is in flight while the chart shows data (§16.5): From |
render | RenderProp<ChartFrameState> | – | From |
shareFormat | Intl.NumberFormatOptions | { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 } | Shares of the previous step. |
status | DataStatus"ready""loading""empty""error" | 'ready'; a ready chart with no table rows renders as `'empty'`. | From |
steprequired | Accessor<T, string> | – | The step's name: a key of the datum, or a |
summary | ReactNode | – | One line under the title, for example "Daily sales, Oct 1–30, vs previous period". From |
tableView | ChartTableView"hidden""details" | 'hidden' | From |
titlerequired | ReactNode | – | The figure's caption and its accessible name. From |
titleVisibility | ChartTitleVisibility"visible""hidden" | 'visible' |
From |
valuerequired | Accessor<T, number | null | undefined> | – | How many reached it; one that is not a finite number counts as 0. |
- Also takes every
<figure>attribute exceptchildren,title.
render takes an element, or, in client code, a function of the props and this state (ChartFrameState):
| Name | Type | Default | Description |
|---|---|---|---|
interactionrequired | ChartInteractionMode"nizivo""none" | – | As passed: whether the child claims keyboard access to its points. |
pendingrequired | boolean | – | |
statusrequired | DataStatus"ready""loading""empty""error" | – | The status as rendered: |
step and value are an Accessor: a key of the datum (step="name", which a Server Component
can pass), or a (datum, index) => value function, as in every chart. format defaults to
Intl.NumberFormat's own options and shareFormat to a percent with one fraction digit ("20.0%").
labels takes Partial<FunnelChartLabels>, on top of ChartFrame's:
| Key | Default |
|---|---|
step | Step |
value | Count |
share | % of previous |
noShare | — |
Other exports
FunnelChartLabelstypeimport type { FunnelChartLabels } from '@nizivo/charts/funnel-chart'FunnelChart's strings (§16.4), with ChartFrame's. Never concatenated: each is a whole label.
Name Type Default Description noSharerequiredstring– In place of the first step's share, which has no step before it.
sharerequiredstring– The data table's share column.
steprequiredstring– The data table's first column header.
valuerequiredstring– The data table's count column.
FunnelChartPropstypeimport type { FunnelChartProps } from '@nizivo/charts/funnel-chart'FunnelChart's props: its data and formats in the charts' one shape (
_core/api.ts) and ChartFrame's, the<figure>'s attributes included. It draws no axes and no legend, is never mirrored and makes no keyboard claim, soaxes,legend,xDirectionandinteractionare its own.The props of
FunnelChart, listed above.FunnelSteptypeimport type { FunnelStep } from '@nizivo/charts/funnel-chart'One step, measured.
Name Type Default Description labelrequiredstring– ofFirstrequirednumber– Its value over the first step's, 0 to 1: the bar's length.
ofPreviousrequirednumber | null– Its value over the previous step's, or
nullon the first step (and after a zero step).valuerequirednumber– funnelChartLabelsconstantimport { funnelChartLabels } from '@nizivo/charts/funnel-chart'English defaults (§16.4); pass
labelsto translate per instance.const funnelChartLabels: FunnelChartLabelsfunnelStepsfunctionimport { funnelSteps } from '@nizivo/charts/funnel-chart'The steps in input order, with a value that is not a finite number taken as 0; negative values are 0 too.
ofFirstis clamped to 0–1 (a later step larger than the first draws a full bar), and is 0 throughout when the first step is 0.function funnelSteps(items: ReadonlyArray<{ label: string; value: number; }>): FunnelStep[]
Data attributes and slots
Inside the ChartFrame root, which carries
nz-funnel-chart (with axes="none" and interaction="none"):
| Element | Attributes |
|---|---|
ol.nz-funnel-chart-steps | aria-hidden; one li per step, data-slot="funnel-chart-step" |
svg.nz-funnel-chart-track | data-slot="funnel-chart-track": the track, and the bar as a rect |
rect.nz-funnel-chart-bar | data-slot="funnel-chart-bar", width the share of the first step in percent |
CSS hooks
| Hook | Default | Read as |
|---|---|---|
--nz-funnel-chart-bar | --nz-chart-primary |
|
--nz-funnel-chart-track | --nz-chart-track |
|
Layers: nz.components. Classes: .nz-chart-frame-body, .nz-funnel-chart, .nz-funnel-chart-bar, .nz-funnel-chart-label, .nz-funnel-chart-share, .nz-funnel-chart-step, .nz-funnel-chart-steps, .nz-funnel-chart-track, .nz-funnel-chart-track-fill, .nz-funnel-chart-value.
Tokens this stylesheet reads (12)
--nz-chart-primary, --nz-chart-track, --nz-font-label-m, --nz-font-mono-m, --nz-font-numeric-m, --nz-space-3, --nz-space-4, --nz-text-brand, --nz-text-primary, --nz-text-secondary, --nz-text-tertiary, --nz-tracking-label-m
The body sizes to its rows rather than ChartFrame's aspect ratio. While loading, empty or failed it keeps Figma's four-step height, so nothing moves when the data arrives.
DonutChart
Share of a whole, drawn on the server. At most three categories plus "Other", in fixed colours that never cycle, with a legend of values and shares beside the ring.
Heatmap
A value for each row and column, such as orders by day and hour, as a grid of cells on a five-step ramp with a "Fewer … More" scale. Drawn on the server, with no client JavaScript, and never the only view of the data.