Charts
WaterfallChart
How a total got from one figure to another.
When to reach for it
Examples
default
| name | Change | Running total |
|---|---|---|
| FY24 ARR | 4,200 | 4,200 |
| New business | 1,180 | 5,380 |
| Expansion | 640 | 6,020 |
| Downgrades | -310 | 5,710 |
| Churn | -820 | 4,890 |
| FY25 ARR | 0 | 4,890 |
subtotals
| name | Change | Running total |
|---|---|---|
| Revenue | 8,400 | 8,400 |
| COGS | -3,100 | 5,300 |
| Gross profit | 0 | 5,300 |
| Sales | -1,450 | 3,850 |
| R&D | -1,900 | 1,950 |
| G&A | -720 | 1,230 |
| Operating profit | 0 | 1,230 |
Notes
How a total got from one figure to another — an opening balance, the signed contributions that moved it, and where it closed.
The form for "why did this change", which a pair of bars cannot answer and a pie chart answers wrongly, because contributions can be NEGATIVE and a slice cannot. Reach for <BarChart> when the parts do not have to add up to the gap between two totals, and for <FunnelChart> when the quantity only ever shrinks.
The connectors are the claim to be careful about. They draw the steps as a sequence — this happened, then this — and most breakdowns are not sequential at all: churn and expansion in the same month are simultaneous, and the order they are listed in is an editorial choice. The arithmetic survives any order; the STORY does not, and a reader will take the leftmost bar as the first cause. Two related traps: the intermediate bars are floating lengths read against no baseline, so a small step high up the cascade is hard to compare with a large one near zero; and any step that is itself a net of two larger opposing movements is invisible as such. Where the order is arbitrary, say so in the description.
Recharts earns its place for the axes, grid and tooltip. Each bar is a floating range — from the running total to the new one — with a custom shape over it, because Recharts has no waterfall mark and the connectors have to be drawn from the same geometry as the bars they join.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| childrenrequired | ReactNode | The composed parts — axes, grid, tooltip, and `<WaterfallChart.Bars>`. | |
| datarequired | WaterfallStep[] | The steps, in the order they are applied. Order is the arithmetic: the chart does not sort, because a different order is a different total at every intermediate bar. | |
| titlerequired | string | What the chart shows, in a sentence a reader could act on. Required, and announced to a screen reader even when it is not printed. | |
| chartProps | ComponentProps<typeof RechartsBarChart> | Escape hatch onto the raw Recharts chart element. | |
| className | string | Merged onto the figure, last, so a call site can size or space it. | |
| config | ChartConfig | DEFAULT_CONFIG | The single series — its label and its paint. Only the FIRST entry is read; a waterfall has one quantity and as many bars as it has steps. |
| description | ReactNode | A line under the title — the unit, the window, the caveat. | |
| empty | ChartEmptyProps | false | What the chart shows when it has nothing to draw. `false` keeps the axes, for a chart whose emptiness is itself the reading. | |
| formatValue | (value: number) => string | defaultTick | Formats every number the chart prints — ticks, labels, tooltip, table. |
| hideDataTable | boolean | false | Drops the hidden table view. Only correct when the page prints the data itself. |
| showTitle | boolean | Prints the title above the plot instead of hiding it from sight. |
Types
export type WaterfallStepType = 'delta' | 'total'
export type WaterfallDirection = 'increase' | 'decrease' | 'total'Accessibility
- The connectors draw the steps as a sequence, and most breakdowns are not sequential — churn and expansion in the same month are simultaneous, and a reader takes the leftmost bar as the first cause. Where the order is arbitrary, say so in description.
- Intermediate bars are floating lengths read against no baseline, so a small step high up the cascade is hard to compare with a large one near zero. Total bars sit on the axis and are the only ones a reader can read absolutely.
- Direction is carried by the label’s sign and the bar’s texture as well as its position, so the reading survives greyscale and forced colours.
- A closing bar with no value is computed from the deltas, which keeps the arithmetic in the data rather than in the caller’s head.