Charts
Sankey Chart
A directed flow whose link widths show quantities moving between named stages.
When to use it: Use it for journeys, transfers, energy, budgets, and other acyclic flows.
Example
import {
ChartDescription,
ChartTable,
ChartTitle,
ChartTooltip,
SankeyChart,
SankeyChartLink,
SankeyChartNode,
SankeyChartPlot,
} from "@comp0/react";
const journeyNodes = [
{ id: "visit", label: "Site visit" },
{ id: "browse", label: "Browse products" },
{ id: "search", label: "Use search" },
{ id: "cart", label: "Add to cart" },
{ id: "leave", label: "Leave site" },
{ id: "purchase", label: "Purchase" },
] as const;
const journeyLinks = [
{ source: "visit", target: "browse", value: 62 },
{ source: "visit", target: "search", value: 38 },
{ source: "browse", target: "cart", value: 31 },
{ source: "browse", target: "leave", value: 31 },
{ source: "search", target: "cart", value: 24 },
{ source: "search", target: "leave", value: 14 },
{ source: "cart", target: "purchase", value: 43 },
{ source: "cart", target: "leave", value: 12 },
] as const;
const nodeColors = [
"fill-teal-700 dark:fill-teal-300",
"fill-sky-600 dark:fill-sky-400",
"fill-violet-600 dark:fill-violet-400",
"fill-amber-500 dark:fill-amber-400",
"fill-rose-500 dark:fill-rose-400",
"fill-emerald-600 dark:fill-emerald-400",
] as const;
const linkColors: Record<string, string> = {
visit:
"stroke-teal-700/45 data-connected:stroke-teal-700/80 dark:stroke-teal-300/35 dark:data-connected:stroke-teal-300/75",
browse:
"stroke-sky-600/45 data-connected:stroke-sky-600/80 dark:stroke-sky-400/35 dark:data-connected:stroke-sky-400/75",
search:
"stroke-violet-600/45 data-connected:stroke-violet-600/80 dark:stroke-violet-400/35 dark:data-connected:stroke-violet-400/75",
cart: "stroke-amber-500/45 data-connected:stroke-amber-500/80 dark:stroke-amber-400/35 dark:data-connected:stroke-amber-400/75",
leave:
"stroke-rose-500/45 data-connected:stroke-rose-500/80 dark:stroke-rose-400/35 dark:data-connected:stroke-rose-400/75",
purchase:
"stroke-emerald-600/45 data-connected:stroke-emerald-600/80 dark:stroke-emerald-400/35 dark:data-connected:stroke-emerald-400/75",
};
const formatPeople = (value: number) => `${value}k`;
export function Example() {
return (
<SankeyChart
nodes={journeyNodes}
links={journeyLinks}
nodeLabel="Journey step"
valueLabel="People"
formatValue={formatPeople}
className="w-full max-w-2xl rounded-lg has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-4 has-[:focus-visible]:outline-teal-600 dark:has-[:focus-visible]:outline-teal-400"
>
<ChartTitle className="text-base font-semibold text-zinc-950 dark:text-zinc-50">
Customer journey
</ChartTitle>
<SankeyChartPlot
aria-label="Sankey chart showing customer movement from site visit to purchase or exit"
className="mx-auto mt-5 w-full max-w-xl overflow-visible"
>
{({ links, nodes }) => (
<>
<g role="presentation" data-slot="sankey-chart-links">
{links.map((link) => {
const targetColor = linkColors[link.value.target] ?? "stroke-zinc-400/45";
return (
<SankeyChartLink key={link.index} link={link} className={targetColor}>
<path d={link.path} fill="none" strokeWidth={link.width} />
</SankeyChartLink>
);
})}
</g>
<g role="presentation" data-slot="sankey-chart-nodes">
{nodes.map((node) => (
<SankeyChartNode key={node.value.id} node={node} className="group outline-none">
<rect
x={node.x}
y={node.y}
width={node.width}
height={node.height}
className={`${nodeColors[journeyNodes.findIndex((value) => value.id === node.value.id)]} stroke-transparent group-data-active:stroke-zinc-950 dark:group-data-active:stroke-white`}
rx="1"
strokeWidth="2"
vectorEffect="non-scaling-stroke"
/>
</SankeyChartNode>
))}
</g>
<g aria-hidden="true" pointerEvents="none" data-slot="sankey-chart-labels">
{nodes.map((node) => (
<text
key={node.value.id}
x={node.x < 60 ? node.x : node.x + node.width}
y={node.y - 1.5}
textAnchor={node.x < 60 ? "start" : "end"}
className="fill-zinc-700 text-[3px] dark:fill-zinc-300"
>
{node.value.label}
</text>
))}
</g>
</>
)}
</SankeyChartPlot>
<ChartDescription className="mt-5 text-sm text-zinc-600 dark:text-zinc-400">
Search visitors reach the cart at a higher rate, while 43 thousand people complete a
purchase.
</ChartDescription>
<ChartTable className="mt-4 w-full border-collapse text-left text-sm [&_td]:border-t [&_td]:border-zinc-200 [&_td]:py-2 [&_th]:border-zinc-200 [&_th]:py-2 dark:[&_td]:border-zinc-800 dark:[&_th]:border-zinc-800 [&_caption]:sr-only" />
<ChartTooltip
placement="right"
className="pointer-events-none z-50 rounded-md bg-zinc-950 px-2 py-1 text-sm text-white shadow-lg dark:bg-zinc-50 dark:text-zinc-950"
>
{(details) => {
if (details.kind !== "sankey") return details.label;
return `${details.value.label}: ${details.formattedIncoming} in, ${details.formattedOutgoing} out`;
}}
</ChartTooltip>
</SankeyChart>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
SankeyChart
Native figure validating an acyclic flow and sharing nodes, links, labels, and formatting. Owns a DOM element.
ChartTitle
Native figcaption that visibly names the figure. Owns a DOM element.
SankeyChartPlot / SankeyChartLink / SankeyChartNode
Layered SVG nodes connected by proportional-width flow paths. Owns a DOM element.
ChartDescription
Visible prose summarizing important movement or loss. Owns a DOM element.
ChartTable
Native table that renders a complete default from the chart data: a caption from the ChartTitle text or axis labels, column headers, and one row per value. Pass children for a custom body. Owns a DOM element.
ChartTooltipOptional
Optional incoming and outgoing node detail shown on hover or focus. Owns a DOM element.
Step by step
- 1
Add the main part
Start SankeyChart with uniquely identified nodes and positive links between them.
- 2
Add the supporting parts
Render links behind SankeyChartNode marks; node arrow keys move upstream, downstream, and within a stage.
- 3
Make the behavior clear
Include a native flow table because link widths alone are not an exact or sufficient text alternative.
Exampletsx <SankeyChart nodes={nodes} links={links} nodeLabel="Step" valueLabel="People"> <ChartTitle>Customer journey</ChartTitle> <SankeyChartPlot aria-label="Sankey chart of the customer journey"> {({ links, nodes }) => ( <> {links.map((link) => ( <SankeyChartLink link={link}> <path d={link.path} /> </SankeyChartLink> ))} {nodes.map((node) => ( <SankeyChartNode node={node}> <rect x={node.x} y={node.y} width={node.width} height={node.height} /> </SankeyChartNode> ))} </> )} </SankeyChartPlot> <ChartTable /> <ChartTooltip /> </SankeyChart>;
Keyboard
- ⇥
- Enters the chart at its current node and leaves with one more Tab.
- ←→
- Moves upstream or downstream along a connected flow.
- ↑↓
- Moves between nodes in the same stage.
- HomeEnd
- Moves to the first or last node.
- Esc
- Dismisses an open ChartTooltip.
Forms and accessibility
Charts are descriptive content and do not create form values.
Accessibility checklist
- Give the plot an aria-label naming the flow and population or quantity.
- Keep links behind nodes and increase connected-link contrast when a node is active.
- Wrap nodes in SankeyChartNode; left and right move along connected flows while up and down move within a stage.
- Each node announces incoming and outgoing totals and connection counts.
- Include a native table listing every source, target, and exact flow value.
- Do not encode flow meaning through color alone.
API reference
import { ChartDescription, ChartTable, ChartTitle, ChartTooltip, SankeyChart, SankeyChartLink, SankeyChartNode, SankeyChartPlot } from "@comp0/react";SankeyChart
DOM elementNative figure validating an acyclic flow and sharing nodes, links, labels, and formatting.
| Prop | Type | Description |
|---|---|---|
nodes | readonly SankeyChartNodeValue[] | Uniquely identified, visibly labelled stages. |
links | readonly SankeyChartLinkValue[] | Positive directed flows between known nodes. |
nodeLabel | string | Visible headings for node and flow table values. |
valueLabel | string | Visible headings for node and flow table values. |
formatValue | (value: number) => string | Formats node totals and table cells. |
ChartTitle
DOM elementNative figcaption that visibly names the figure.
SankeyChartPlot / SankeyChartLink / SankeyChartNode
DOM elementLayered SVG nodes connected by proportional-width flow paths.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Concise text alternative naming the flow and quantity. |
children | (state: SankeyChartPlotState) => ReactNode | Custom renderer receiving positioned nodes and links. |
link | SankeyChartLinkState | Link state passed to the visual SankeyChartLink wrapper. |
node | SankeyChartNodeState | Node state passed to the keyboard-reachable SankeyChartNode. |
ChartDescription
DOM elementVisible prose summarizing important movement or loss.
ChartTable
DOM elementNative table that renders a complete default from the chart data: a caption from the ChartTitle text or axis labels, column headers, and one row per value. Pass children for a custom body.
ChartTooltip
OptionalDOM elementOptional incoming and outgoing node detail shown on hover or focus.
| Prop | Type | Description |
|---|---|---|
placement | PopoverPlacement | Side of the active mark; defaults to "top". |
offset | number | Distance from the active mark; defaults to eight pixels. |
children | ReactNode | (details: ChartValueDetails) => ReactNode | Custom content receiving the active node's formatted details. |
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
SankeyChartPlot / SankeyChartLink / SankeyChartNode
| Style hook | Meaning |
|---|---|
[data-node-id] | The id of the node this mark represents. |
[data-source] | The id of the node this link leaves. |
[data-target] | The id of the node this link enters. |
[data-connected] | The link touches the active node. |
[data-layer] | The node's zero-based flow stage. |
[data-active] | The node currently reached by pointer or keyboard. |
ChartTooltip
| Style hook | Meaning |
|---|---|
[data-open] | A node tooltip is visible. |