shadcn.io is an independent project built on shadcn/ui
Installation
The command adds the example above to components/charts/ and, the first time, the chart kit to components/ui/charts/. Both are source files in your project from then on: read them, change them, delete what you don't use.
Usage
data has nodes, each with a name, and links that point at nodes by index with a value. Links with a value of zero or less are left out.
<script setup lang="ts">
import {
SankeyChart,
type SankeyData,
SankeyLink,
SankeyNode,
SankeyTooltip,
} from "@/components/ui/charts";
const data: SankeyData = {
nodes: [
{ name: "Search" },
{ name: "Social" },
{ name: "Pricing" },
{ name: "Docs" },
{ name: "Signed up" },
{ name: "Left" },
],
links: [
{ source: 0, target: 2, value: 3200 },
{ source: 0, target: 3, value: 1800 },
{ source: 1, target: 2, value: 1400 },
{ source: 1, target: 3, value: 600 },
{ source: 2, target: 4, value: 1900 },
{ source: 2, target: 5, value: 2700 },
{ source: 3, target: 4, value: 900 },
{ source: 3, target: 5, value: 1500 },
],
};
const margin = { left: 100, right: 100 };
const formatVisits = (value: number) =>
`${value.toLocaleString("en-US")} visits`;
</script>
<template>
<SankeyChart
ariaLabel="Visits from source to sign-up"
:data="data"
:margin="margin"
>
<SankeyLink />
<SankeyNode :format-value="formatVisits" />
<SankeyTooltip value-label="Visits" />
</SankeyChart>
</template>Anatomy
SankeyNode, SankeyLink and SankeyTooltip render nothing themselves: they configure the chart. Leave one out and its part keeps its defaults.
<SankeyChart :data="data" :margin="margin">
<SankeyLink :stroke-opacity="0.6" />
<SankeyNode :format-value="formatVisits" />
<SankeyTooltip value-label="Visits" />
</SankeyChart>SankeyChart: Lays out the nodes and links with d3-sankey and draws them.SankeyNode: The node bars and their labels: color, corners, label direction and value format.SankeyLink: The links: a gradient from source to target by default, or one color, or a pattern.SankeyTooltip: The panel for a hovered node or link, the default one or yours.
Colors
Nodes take --chart-1 to --chart-5 in order, and each link fades from its source's color to its target's. To color by something else, such as the stage, pass the same getNodeColor to SankeyNode and SankeyLink.
:useGradient="false" paints each link in the color of the node it leaves. stroke paints them all one color, and getLinkColor picks one per link.
Labels
Names sit beside the nodes with each value underneath. labelOrientation="vertical" turns them to run along the node, which needs less room at the sides. :showLabels="false" hides them for a compact chart you read by hovering.
Values read "N sessions" until you pass formatValue to SankeyNode. A node's value is the larger of what flows in and what flows out.
Patterned links
Put SVG patterns in SankeyLink's patterns, a PatternPreset or your own <pattern>, and return a pattern's id from getLinkPattern. A link that gets null keeps its color.
Driving the hover
hoveredNodeIndex sets the hovered node from outside the chart, and onNodeHoverChange reports the chart's own. Keep one index in state and a row of buttons can light everything a node feeds.
Customization
The kit is in your project, in components/ui/charts/: your framework's components over a shared core/. Change a default there and every chart picks it up.
class goes on the chart's container, so size and spacing are utility classes.
SankeyTooltip takes nodeContent and linkContent. Each gets the hovered item and its index, and what it returns fills the panel. For the default panel, formatValue and valueLabel change the number and its label.
The default margin leaves 180 px on each side for labels, which is a lot on a small card. Pass your own, as every example does, and nodeWidth and nodePadding for the bars.
<SankeyTooltip>
<template #link-content="{ link }">
<div class="grid gap-1">
<span class="font-medium">
{{ data.nodes[link.source]?.name }} → {{ data.nodes[link.target]?.name }}
</span>
<span class="text-muted-foreground">
{{ link.value.toLocaleString("en-US") }} visits
</span>
</div>
</template>
</SankeyTooltip>Where a part renders content of its own, you pass it in. In Vue:
SankeyTooltip:<template #link-content="{ link, index }">SankeyTooltip:<template #node-content="{ node, index }">
Loading state
The Sankey diagram has no loading state of its own. Until the data arrives, render a Skeleton with the chart's aspectRatio, 2 / 1 unless you set one, so the card keeps its height.
Animation
Nodes grow in one after another while their labels slide in from the side. A beat later the links draw left to right, staggered across the flows. Each part runs for animationDuration, 1100 ms by default.
enterTransition takes a spring or a cubic-bezier tween, in seconds, in place of the default curve. Change revealSignature to play the entrance again.
Theming
Every color in the kit is one of your theme's tokens or a mix of them, except --chart-positive, a fixed green. A preset or the .dark class restyles the chart, and overriding a token in your CSS changes it everywhere.
Links are drawn at strokeOpacity 0.5, a lighter version of their nodes' colors, and drop to 0.1 while something else is hovered. Node names use your --foreground.
| Token | Default | Paints |
|---|---|---|
--chart-1 | your theme's | the first series |
--chart-2 | your theme's | the second series |
--chart-3 | your theme's | the third series |
--chart-4 | your theme's | the fourth series |
--chart-5 | your theme's | the fifth series |
--chart-tooltip-background | --popover | the tooltip panel |
--chart-tooltip-muted | --muted-foreground | the tooltip's labels |
:root {
--chart-tooltip-background: var(--card);
}Accessibility
The chart is an image named by ariaLabel ("Sankey chart" by default). The widths carry the numbers, so say the main flow in ariaDescription: "Two thirds of search traffic reaches pricing."
The keyboard visits the nodes column by column, top to bottom, then the links in the same order. Each one shows its tooltip and lights what it connects to, as on hover.
- Tab
- Moves focus to the chart.
- Arrow Right
- Moves to the next node or link. ArrowDown does the same.
- Arrow Left
- Moves to the previous node or link. ArrowUp does the same.
- Home
- Jumps to the first node.
- End
- Jumps to the last link.
API reference
Vue takes the same props as React, in camelCase or kebab-case, with class for className. Render props are scoped slots (#content), and callbacks such as onHoverChange can be written as listeners (@hover-change).
SankeyChart
dataSankeyData
Nodes and links to draw. Links reference nodes by index; links with a non-positive or non-finite value are ignored, and the chart is empty when no positive link remains.
margin?Partial<Margin>
Chart margins in pixels. Any side you omit keeps its default; the wide left and right defaults leave room for node labels.
- Type
Partial<Margin>- Default
{ top: 40, right: 180, bottom: 40, left: 180 }
animationDuration?number
Duration of the enter animation in milliseconds.
- Default
1100
enterTransition?Transition
Enter transition (a spring or a cubic-bezier tween), in seconds. Overrides animationDuration when it sets a duration.
- Default
a 1.1s tween on the reveal curve
revealSignature?string
Changing this value replays the enter animation.
- Default
""
aspectRatio?string
Aspect ratio of the chart as "width / height".
- Default
"2 / 1"
nodeWidth?number
Width of the node bars in pixels.
- Default
16
nodePadding?number
Vertical gap between nodes of the same column in pixels.
- Default
24
class?string
Additional class name for the chart container.
children?ReactNode
SankeyNode, SankeyLink and SankeyTooltip to configure the chart, plus any elements that call useSankey().
hoveredNodeIndex?number | null
Controlled hovered node index, for example from a legend. Overrides hover from the chart surface; null clears it.
- Type
number | null
onNodeHoverChange?(index: number | null) => void
Called with the hovered node index (or null) when hover changes on the chart surface.
- Type
(index: number | null) => void
ariaLabel?string
Accessible label of the chart.
- Default
"Sankey chart"
ariaDescription?string
Longer accessible description of the chart.
SankeyNode
fill?string
Fill color of the nodes.
- Default
the theme chart colors, by node
lineCap?number
Corner radius of the nodes in pixels.
- Default
4
fadedOpacity?number
Opacity of nodes not connected to the hovered node or link.
- Default
0.4
showLabels?boolean
Show node names.
- Default
true
showValueLabels?boolean
Show node values under the names.
- Default
true
formatValue?(value: number) => string
Formats the value shown under a node's name. A node's value is the larger of its inflow and outflow.
- Type
(value: number) => string- Default
the value as an integer followed by " sessions"
labelOrientation?SankeyLabelOrientation
Reading direction of the labels outside the nodes.
- Default
"horizontal"
getNodeColor?(node: SankeyPropNode, index: number) => string
Returns the color of a node. Overrides fill.
- Type
(node: SankeyPropNode, index: number) => string
SankeyLink
stroke?string
Solid color for all links. Overrides the gradient.
strokeOpacity?number
Opacity of the links.
- Default
0.5
fadedOpacity?number
Opacity of links not connected to the hovered node or link.
- Default
0.1
useGradient?boolean
Paint each link with a gradient from its source color to its target color.
- Default
true
getNodeColor?(node: SankeyPropNode, index: number) => string
Returns the color of a node, used for the gradient ends.
- Type
(node: SankeyPropNode, index: number) => string
getLinkColor?(link: SankeyPropLink, index: number) => string
Returns a solid color per link. Overrides the gradient.
- Type
(link: SankeyPropLink, index: number) => string
#patterns?slot
Pattern definitions (SVG <pattern> elements) that links can reference by id through getLinkPattern.
getLinkPattern?(link: SankeyPropLink, index: number) => string | null | undefined
Returns the id of a pattern to fill a link with, or null/undefined to use the gradient or solid color. A pattern wins over stroke and getLinkColor.
- Type
(link: SankeyPropLink, index: number) => string | null | undefined
SankeyTooltip
#node-content?slot: { node, index }
Custom tooltip content for a node. Receives the node and its index.
#link-content?slot: { link, index }
Custom tooltip content for a link. Receives the link and its index.
formatValue?(value: number) => string
Formats the values in the default tooltip.
- Type
(value: number) => string- Default
integer formatting
valueLabel?string
Label of the value row in the default node tooltip.
- Default
"Sessions"
class?string
Additional class name for the tooltip.
panelStyle?CSSProperties
Inline styles merged onto the tooltip panel, for example a background or a backdrop blur. A number is in pixels where CSS needs a unit.
exit?boolean
Fade the panel out when the hover ends (over the hover profile); false hides it at once.
- Default
true
Examples
9 examples on Vue, each live with its code and its own install command.





