Skip to content
Nizivo UI

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

Checkout funnelSessions to purchases, October
StepSessions% of previous
Sessions48,210—
Added to cart9,64220.0%
Reached checkout4,62848.0%
Purchased2,91463.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.

Bestellprozess
Werte
SchrittBesuche% vom vorigen
Besuche12.840—
Warenkorb3.21025,0 %
Kasse1.76555,0 %
Gekauft1.10262,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.

Checkout funnel
Loading chart
Checkout funnel

No data to show

Checkout funnel

This chart could not be loaded

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:

  1. Steps keep their input order. A value that is not a finite number, or is below zero, counts as 0.
  2. 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.
  3. 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.

NameTypeDefaultDescription
datarequiredreadonly T[]–

One datum per step, in funnel order.

emptyReactNode–

Replaces the default empty message.

From ChartFrameProps.

errorReactNode–

Replaces the default error message, for example an InlineError with a retry action.

From ChartFrameProps.

formatIntl.NumberFormatOptionsIntl.NumberFormat's own ({})

Counts.

labelsPartial<FunnelChartLabels>–
localestring'en-US'

BCP 47 locale every number is formatted in, once, on the server (§16.4).

pendingboolean–

A refresh is in flight while the chart shows data (§16.5): aria-busy and data-pending.

From ChartFrameProps.

renderRenderProp<ChartFrameState>–

From ChartFrameAttributes.

shareFormatIntl.NumberFormatOptions{ style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 }

Shares of the previous step.

statusDataStatus
"ready""loading""empty""error"
'ready'; a ready chart with no table rows renders as `'empty'`.

From ChartFrameProps.

steprequiredAccessor<T, string>–

The step's name: a key of the datum, or a (datum, index) function.

summaryReactNode–

One line under the title, for example "Daily sales, Oct 1–30, vs previous period".

From ChartFrameProps.

tableViewChartTableView
"hidden""details"
'hidden'

From ChartFrameProps.

titlerequiredReactNode–

The figure's caption and its accessible name.

From ChartFrameProps.

titleVisibilityChartTitleVisibility
"visible""hidden"
'visible'

'hidden' hides the title and summary visually; they still name the figure.

From ChartFrameProps.

valuerequiredAccessor<T, number | null | undefined>–

How many reached it; one that is not a finite number counts as 0.

  • Also takes every <figure> attribute except children, title.

render takes an element, or, in client code, a function of the props and this state (ChartFrameState):

NameTypeDefaultDescription
interactionrequiredChartInteractionMode
"nizivo""none"
–

As passed: whether the child claims keyboard access to its points.

pendingrequiredboolean–
statusrequiredDataStatus
"ready""loading""empty""error"
–

The status as rendered: 'empty' when a ready chart has no rows.

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:

KeyDefault
stepStep
valueCount
share% of previous
noShare—

Other exports

FunnelChartLabels type

import type { FunnelChartLabels } from '@nizivo/charts/funnel-chart'

FunnelChart's strings (§16.4), with ChartFrame's. Never concatenated: each is a whole label.

NameTypeDefaultDescription
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.

FunnelChartProps type

import 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, so axes, legend, xDirection and interaction are its own.

The props of FunnelChart, listed above.

FunnelStep type

import type { FunnelStep } from '@nizivo/charts/funnel-chart'

One step, measured.

NameTypeDefaultDescription
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 null on the first step (and after a zero step).

valuerequirednumber–
funnelChartLabels constant

import { funnelChartLabels } from '@nizivo/charts/funnel-chart'

English defaults (§16.4); pass labels to translate per instance.

const funnelChartLabels: FunnelChartLabels

funnelSteps function

import { 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. ofFirst is 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"):

ElementAttributes
ol.nz-funnel-chart-stepsaria-hidden; one li per step, data-slot="funnel-chart-step"
svg.nz-funnel-chart-trackdata-slot="funnel-chart-track": the track, and the bar as a rect
rect.nz-funnel-chart-bardata-slot="funnel-chart-bar", width the share of the first step in percent

CSS hooks

HookDefaultRead as
--nz-funnel-chart-bar--nz-chart-primary
  • fill on .nz-funnel-chart-bar
--nz-funnel-chart-track--nz-chart-track
  • fill on .nz-funnel-chart-track-fill

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.

On this page