import { SankeyChart } from "@elabs-ai/components-charts";npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-chartsPaste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.
Add the brand-ui SankeyChart chart (from @elabs-ai/components-charts) to this project. 1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`. 2. Look SankeyChart up before writing code: `npx -y @elabs-ai/components-cli docs SankeyChart` prints its real props, variants, what it pairs with and what to avoid. 3. If @elabs-ai/components-charts is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`. 4. Use it here: [the screen or file, and what it should show] 5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop. 6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.
Use it for
- How does it connect or flow? Routes through stages.
- a weighted flow between named nodes, source to target
Avoid
- the nodes have no real flow between them — use a network chart
- A Sankey for a simple two-column comparison — reach for a bar chart.
- Raw colors on nodes/links — resolve the chart tokens so the dark theme reads correctly.
- So many nodes that the labels collide — aggregate the tail into one 'other' node.
Works with
Contains
Used inside
Examples
Default
With Accessible Label
`accessibleLabel` with no `accessibleDescription` (RM-184): the figure gets a generated description — node/link counts, the one fact an `aria-hidden` Sankey body withholds — through the same shared seam `LineChart`/`PieChart`/etc. use.
Controlled Hover
`hoveredIndex` / `onHoverChange` (RM-195, ADR 0042 A.5 rows 28–29): the host owns the hover state and drives the highlight through props, the same contract Pie/Ring/Radar/Legend already use. Hovering a node fires `onHoverChange` with its index; the `<output>` below proves the round trip actually reaches the host, not just that the callback was called.
Loading
`status="loading"` (RM-184): the skeleton + `ChartLoadingLabel`, until the data arrives.
Decorated
The funnel flow at decoration 10 (#255). Each palette node gains a series-pattern fill, so `SankeyNode` renders its `<pattern>` defs from a `.map()` — the second call site a missing list identity used to warn on. Pinned with `DecorationProvider` so a headless run reaches the branch.
Minimal
Pipeline
Threads
Palette
`palette="mono"` (RM-186): nodes, and the links that take their colours, cycle through five steps of the neutral ladder instead of `--chart-1`..`--chart-5`.
API
SankeyChart
| Prop | Type | Description |
|---|---|---|
messages | ChartMessages | messages group (RM-187): this chart's own words, keyed by the ui catalogue's `charts.*` message keys. A key set here wins over the `LocaleProvider`; every other key reads the catalogue as before. |
datarequired | SankeyData | Sankey data with nodes and links |
margin | Partial<Margin> | Chart margins |
animationDuration | number | Animation duration in milliseconds. Default: 1100 |
enterTransition | Transition | Motion enter transition (spring or cubic-bezier tween). |
revealSignature | string | Signature of motion URL state — triggers enter replay when it changes. |
aspectRatio | string | Aspect ratio as "width / height". Default: "2 / 1" |
plotHeight | Responsive<ChartPlotHeight> | The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. Wins over `aspectRatio`, which stays an alias. |
nodeWidth | number | Node width in pixels. Default: 16 |
nodePadding | number | Node padding in pixels. Default: 24 |
className | string | Additional class name for the container |
childrenrequired | ReactNode | Child components (SankeyNode, SankeyLink, SankeyTooltip) |
hoveredIndex | number | null | Controlled hovered node index (e.g. from ChartLegend). |
hoveredNodeIndex | number | null | Controlled hovered node index. @deprecated Since 6.0.0, use `hoveredIndex`. Removed in 7.0.0. |
onHoverChange | (index: number | null) => void | Called when node hover changes from the chart surface. |
onNodeHoverChange | (index: number | null) => void | Called when node hover changes from the chart surface. @deprecated Since 6.0.0, use `onHoverChange`. Removed in 7.0.0. |
mode | SankeyMode | Rendering mode (RM-037). `"aggregate"` (default) is today's behavior — one edge per node pair via `d3-sankey`, rendered with `SankeyLink`. `"threads"` additionally accepts a `path: string[]` (ordered node names) on each `data.links` record and renders one polyline per record via `SankeyThreadLinks` — node positions come from the SAME aggregate layout, so switching modes never moves a node. Omitting `mode` (or passing `"aggregate"`) is byte-identical to pre-RM-037 output. |
accessibleLabel | ChartA11yProps["accessibleLabel"] | Accessible name for the chart region. Combine with a generated description (RM-184): setting this with no `accessibleDescription` gets an auto summary — `"Sankey diagram, 5 nodes, 8 links"` — the counts an `aria-hidden` Sankey body withholds, the same shared seam `LineChart`, `AreaChart`, `BarChart`, `ScatterChart` and `PieChart` already use. |
accessibleDescription | ChartA11yProps["accessibleDescription"] | Supplemental description read by AT. Overrides the generated summary above. |
status | ChartStatus | Show the loading skeleton until the data is ready (ADR 0042 §9). Default `"ready"`. |
empty | ChartEmptyState | Title and message shown when there is nothing to plot. |
palette | ChartPalette | Colour ramp for the nodes (RM-186): five colours through `resolvePalette`, cycled; links and threads follow their source node. A `SankeyNode` `fill` / `getNodeColor` still wins. Unset: `--chart-1` … `--chart-5`, as before. |
SankeyLink
| Prop | Type | Description |
|---|---|---|
stroke | string | Stroke color for links (overrides gradient). Default: uses gradient |
strokeOpacity | number | Stroke opacity. Default: 0.5 |
fadedOpacity | number | Opacity when another link/node is hovered. Default: 0.1 |
useGradient | boolean | Use gradient from source to target color. Default: true |
getNodeColor | (node: SankeyNodeType<SankeyNodeDatum, SankeyLinkDatum>, index: number) => string | Custom function to get node color (for gradient) |
getLinkColor | (link: SankeyLinkType<SankeyNodeDatum, SankeyLinkDatum>, index: number) => string | Custom link color function (overrides gradient) |
patterns | React.ReactNode | Pattern definitions to render in defs. Use @visx/pattern components (PatternLines, PatternCircles, etc.) |
getLinkPattern | ( link: SankeyLinkType<SankeyNodeDatum, SankeyLinkDatum>, index: number, ) => string | null | undefined | Return pattern ID for a link, or null/undefined to use gradient/solid color |
SankeyNode
| Prop | Type | Description |
|---|---|---|
fill | string | Fill color for nodes. Default: uses theme colors |
lineCap | number | Corner radius for nodes. Default: 4 |
fadedOpacity | number | Opacity when another node/link is hovered. Default: 0.4 |
showLabels | boolean | Show node labels. Default: true |
getNodeColor | (node: SankeyNodeType<SankeyNodeDatum, SankeyLinkDatum>, index: number) => string | Custom node color function |
formatValue | (value: number) => string | The value line under each node's name, from the node's flow. Default: `"<n> sessions"` (the unit this chart shipped with). Pass your own to name the unit the links actually count — `(v) => \`${v} invoices\``. The same text is what the label-fit measurement reserves room for. |
SankeyThreadLinks
| Prop | Type | Description |
|---|---|---|
getThreadColor | ( route: number[], nodes: SankeyNodeType<SankeyNodeDatum, SankeyLinkDatum>[], ) => string | Stroke color per thread. Default: the route's source node's palette color. |
fadedOpacity | number | Opacity applied to every OTHER thread while one is hovered, focused or pinned. Default: 0.035 |
hitTwinWidth | number | Fat hit-twin stroke width in px (the invisible pointer hit area). Default: 9 |
className | string | Additional class name for the thread group. |
SankeyTooltip
| Prop | Type | Description |
|---|---|---|
nodeContent | (props: { node: SankeyNode<SankeyNodeDatum, SankeyLinkDatum>; index: number; }) => React.ReactNode | Custom content renderer for node tooltips |
linkContent | (props: { link: SankeyLink<SankeyNodeDatum, SankeyLinkDatum>; index: number; }) => React.ReactNode | Custom content renderer for link tooltips |
formatValue | (value: number) => string | Value formatter function |
className | string | Custom class name |
Theming
The tokens each state resolves to. Change the token and every theme follows.
| State | Token |
|---|---|
| series | --chart-1 … --chart-5 |
| mode | aggregate (default) | threads |