Waterfall Chart
- Stable
- WCAG 2.2 evidence
- RTL
A waterfall (bridge) chart: how a start value becomes an end value through increases and
decreases. Data driven: steps in, SVG out; the same props render the static SVG on the server and the plot is laid out for the
width of its container. Accessibility: the figure is named by title and described by summary; the data table lists every step with its type in words, its
change and the running total (WCAG 1.1.1, 1.3.1). Increases, decreases and totals are told apart by more than colour (1.4.1): the
value label carries a sign ("+52", "-20"), decreases are hatched, totals are dotted, the legend names the three, and the tooltip,
point label and table say the type. Connectors are decorative. The plot is ONE tab stop: Left and Right move between steps, Home
and End jump to the ends, Page Up and Page Down skip a tenth; each step is announced with its type, change and running total. The
tooltip shows on hover and focus, stays while hovered and closes with Escape (1.4.13, 2.1.1). Step names rotate or thin out for the
container width (1.4.10); the plot stays left-to-right in right-to-left pages; the draw-in is behind motion-safe:. Consumer duties: a title that asks the question, a summary that says the start, the end and the biggest change, units in
valueFormat, meaningful step names.
import { WaterfallChart } from "@nexera-ui/react";- 12
- examples
- 21
- props
- 6
- live controls
- 1
- platform
- 9
- WCAG criteria
- 1
- block uses it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart />Examples 11
The same examples as Storybook, rendered live. Open Code to copy one.
With kpi
A KPI row with the net change next to the bridge.
import { DeltaBadge, IconButton, WaterfallChart } from "@nexera-ui/react";
import { LuEllipsis } from "react-icons/lu";
const menu = <IconButton aria-label="More options" variant="ghost" icon={<LuEllipsis />} />;
<WaterfallChart
kpi={{ value: "128", delta: <DeltaBadge value="+33% vs Jan" trend="up-good" /> }}
actions={menu}
/>;
Without patterns
Patterns and value labels off: the types stay in the legend, the tooltip, the point labels and the table.
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart />;
Negative levels
A bridge that goes below zero: the zero line is stronger, a change that crosses it spans both sides, the end total hangs down from it.
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart
title="Cash position"
description="Thousands, Q3"
summary="Cash fell from 10 to minus 30 after 40 of spend."
steps={[
{ label: "Opening", value: 10, kind: "total" },
{ label: "Receipts", value: 25 },
{ label: "Payroll", value: -55 },
{ label: "Rent", value: -10 },
{ label: "Closing", kind: "total", isHighlighted: true },
]}
/>;
Missing values
A step without a value draws no bar and reads "No data" in the tooltip and the table; the running total carries on.
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart
steps={[
{ label: "Start", value: 96, kind: "total" },
{ label: "Hires", value: 52 },
{ label: "Exits", value: null },
{ label: "Transfers in", value: 4 },
{ label: "End", kind: "total", isHighlighted: true },
]}
/>;
Loading
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart state={{ status: "loading" }} />;
Empty
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart
steps={[]}
state={{
status: "empty",
title: "No headcount changes in 2026",
description: "Choose another year.",
}}
/>;
Error state
import { Button, WaterfallChart } from "@nexera-ui/react";
<WaterfallChart
state={{
status: "error",
title: "Couldn't load the bridge",
description: "Check your connection and try again.",
action: (
<Button variant="secondary" size="sm">
Try again
</Button>
),
}}
/>;
Single point
One step: a single bar, never wider than 64 px, with no connector.
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart
title="Hires this month"
description="October"
summary="8 hires in October."
steps={[{ label: "Hires", value: 8 }]}
/>;
Many points
42 steps: names thin out to what fits, value labels that do not fit their slot are left out, and the keys still reach every step.
import { WaterfallChart } from "@nexera-ui/react";
const MONTH_STEPS: WaterfallStep[] = [
{ label: "Start", value: 500, kind: "total" },
...Array.from({ length: 40 }, (_, i) => ({
label: `Week ${String(i + 1)}`,
value: [12, -7, 3, -5, 9, -2][i % 6] ?? 0,
})),
{ label: "End", kind: "total" as const, isHighlighted: true },
];
<WaterfallChart
title="Weekly headcount movements"
description="Forty weeks"
summary="Headcount rose from 500 to 524 over forty weeks."
steps={MONTH_STEPS}
/>;
Long labels
Long step names rotate by 45 degrees; the title, legend and tooltip wrap; the table keeps the full names.
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart
title="Net change in headcount by source of movement across every department and every location during the year"
description="People, from the opening headcount on 1 January to the closing headcount on 31 December"
summary="Hires from agencies and referrals outweighed exits during the first year of employment."
steps={[
{ label: "Opening headcount on 1 January", value: 96, kind: "total" },
{ label: "Hires from recruitment agencies", value: 30 },
{ label: "Hires from employee referrals", value: 22 },
{ label: "Exits in the first year of employment", value: -14 },
{ label: "Transfers to other departments", value: -6 },
{ label: "Closing headcount on 31 December", kind: "total", isHighlighted: true },
]}
/>;
Right to left
Right-to-left page: the card, legend and table mirror, the plot keeps its left-to-right axis, digits and signs follow the locale.
import { WaterfallChart } from "@nexera-ui/react";
<WaterfallChart
title="جسر عدد الموظفين ٢٠٢٦"
description="من ٩٦ إلى ١٢٨ موظفًا"
summary="ارتفع عدد الموظفين من ٩٦ إلى ١٢٨ بصافي زيادة قدره ٣٢."
steps={[
{ label: "البداية", value: 96, kind: "total" },
{ label: "التعيينات", value: 52 },
{ label: "المغادرات", value: -20 },
{ label: "النقل إلى الفريق", value: 4 },
{ label: "النقل من الفريق", value: -4 },
{ label: "النهاية", kind: "total", isHighlighted: true },
]}
labels={{
increase: "زيادة",
decrease: "نقصان",
total: "الإجمالي",
unchanged: "بلا تغيير",
type: "النوع",
change: "التغيير",
}}
table={{ categoryLabel: "الخطوة", label: "عرض البيانات كجدول" }}
/>;
Props 21
Press "Try it" on a card to load that prop into the playground.
21 props shown
steps*Nexerareadonly WaterfallStepInput[]The steps, left to right. A step with a value is a change that floats from the running total (positive increases, negative
decreases); a step with kind: "total" stands on the baseline at the running total, or at its own value. Figma's headcount bridge
is a start total of 96, four changes and an end total.
hasValueLabelsNexerabooleanShow the value above every bar: the change with its sign ("+52", "-20") or the level of a total. Labels that do not fit their slot are left out; the tooltip and the table have every value.
hasConnectorsNexerabooleanDraw a thin line from each bar to the next at the running total. Decorative.
patternsNexerabooleanDraw a fill pattern over the bars (hatch for decreases, dots for totals) in addition to their colour, so the three kinds can be told apart without colour (WCAG 1.4.1).
labelsNexeraPartial<WaterfallLabels>Texts the chart invents: the types of step and the table headers. Translate them.
title*NexerastringChart title: the question the chart answers ("Headcount"). Required: it names the figure, the plot and the data table (WCAG 1.1.1).
descriptionNexeraReactNodeSecondary line under the title.
summary*NexerastringOne or two sentences with what the chart shows and its main message ("Headcount grew from 96 to 128 over twelve months"). Required: it describes the figure for assistive technology (WCAG 1.1.1); the data table has the details.
rangeNexeraChartRangePropsRange switch in the header: 3M, 6M, 12M. Pass the data of the chosen range back in.
actionsNexeraReactNodeExtra controls in the header, after the range switch.
kpiNexeraChartKpiPropsKPI row above the plot: the headline number with an optional label or change badge.
stateNexeraChartPlotStateThe state that replaces the plot: loading (skeleton), empty or error. A chart without a value to plot shows the empty state
by itself; pass { status: "empty", title, description, action } to give it your wording.
tableNexeraChartTableOptionsThe data table that is the text alternative: caption, first-column header and whether it is visible or only for assistive technology.
localeNexerastringLocale of digits and separators.
valueFormatNexeraNumberFormatOptionsIntl.NumberFormat options for values ({ style: "currency", currency: "PKR" }, { style: "percent" }). Serialisable, so it
works from a Server Component; formatValue needs a Client Component.
formatValueNexera(value: number) => stringFormats a value for tooltips, point labels and the table (full precision). Wins over valueFormat.
formatAxisValueNexera(value: number) => stringFormats an axis tick (short: "29M").
heightNexeranumberHeight of the plot in px. The card grows with the header, KPI and legend around it.
isLegendHiddenNexerabooleanHide the legend. The series stay named in the tooltip, the point labels and the table.
plotLabelNexerastringAccessible name of the plot group. Translate it.
noDataLabelNexerastringText for a missing value in the tooltip and the table. Translate it.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
8 direct · 1 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.1Use of ColorLevel A · tested directly
- 1.4.10ReflowLevel AA · tested directly
- 1.4.13Content on Hover or FocusLevel AA · tested directly
- 2.1.1KeyboardLevel A · tested directly
- 2.3.3Animation from InteractionsLevel AAA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
- 4.1.3Status MessagesLevel AA · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
<WaterfallChart className="shadow-sm" />Used in blocks
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.