React Sankey Diagram

Where does the volume actually go? A Sankey answers that in one picture: every ribbon is as wide as the quantity it carries, so a loss you would have missed in a table becomes the thickest thing on screen.

Die yield accounting — lot L2411

9 nodes · 8 flows

Wafer starts25,000 diePost-litho24,100 diePost-etch22,600 dieProbe pass20,150 dieShipped19,780 dieLitho scrap900 dieEtch scrap1,500 dieProbe fail2,450 dieAssembly loss370 die

Hover a flow or a node to trace it.

Installation

npx shadcn@latest add "https://kelvinui.com/registry/sankey-diagram.json"

Props

PropTypeDescription
nodesSankeyNode[]{ id, label, color? }
linksSankeyLink[]{ source, target, value }
unitstringAppended to value labels
heightnumberSVG height (default 340)
formatValue(v: number) => stringValue formatter

Layout, and the thing to check in your data

Columns are assigned by longest path from a root, so flow always travels left to right and a node never appears upstream of something feeding it. A cycle guard keeps a malformed graph from hanging the layout. Ribbons stack in link order on each side of a node, and one scale — set by the busiest column — applies to the whole diagram, so a ribbon twice as thick genuinely carries twice the volume.

The property worth verifying in your own data is conservation: for every intermediate node, inflow should equal outflow, and total sources should equal total sinks. The demo's yield accounting does (25,000 wafer starts in, 25,000 accounted for across shipped and four named scrap buckets). If your numbers do not balance, the diagram will still render — it will just quietly imply that material vanished, which is exactly the error a Sankey is supposed to expose. Add an explicit "unaccounted" node rather than letting the gap hide.

New components every week

Get the week's new Kelvin UI components and templates in one short email. No spam, unsubscribe anytime.