Skip to main content
Urbicon UI

Sankey

Flow diagram for multi-stage data: nodes split into layers, paths drawn to scale as cubic Bezier curves, with an sr-only table as the screen-reader fallback.

Playground

blocksauthtableformdisplayfeedbackailayoutnavigationoverlayactiondatastablebetaexperimental
Sankey diagram: 15 nodes, 30 links — blocks → form: 17; blocks → display: 15; blocks → ai: 10; blocks → layout: 10; blocks → action: 8; blocks → navigation: 8; blocks → overlay: 8; blocks → feedback: 7; auth → form: 8; auth → feedback: 4; auth → display: 1; auth → data: 1; table → data: 1; form → stable: 20; form → beta: 5; display → beta: 11; display → stable: 5; ai → experimental: 10; layout → stable: 7; layout → experimental: 2; layout → beta: 1; action → stable: 6; action → beta: 2; navigation → stable: 5; navigation → beta: 3; overlay → stable: 4; overlay → beta: 4; feedback → stable: 10; feedback → beta: 1; data → stable: 2
SourceTargetValue
blocksform17
blocksdisplay15
blocksai10
blockslayout10
blocksaction8
blocksnavigation8
blocksoverlay8
blocksfeedback7
authform8
authfeedback4
authdisplay1
authdata1
tabledata1
formstable20
formbeta5
displaybeta11
displaystable5
aiexperimental10
layoutstable7
layoutexperimental2
layoutbeta1
actionstable6
actionbeta2
navigationstable5
navigationbeta3
overlaystable4
overlaybeta4
feedbackstable10
feedbackbeta1
datastable2
<script lang="ts">
  import { Sankey } from '@urbicon-ui/blocks';

  const nodes = [
    { id: 'blocks', label: 'blocks', intent: 'primary' },
    { id: 'auth', label: 'auth', intent: 'secondary' },
    { id: 'table', label: 'table', intent: 'neutral' },
    { id: 'form', label: 'form' },
    { id: 'display', label: 'display' },
    { id: 'feedback', label: 'feedback' },
    { id: 'ai', label: 'ai' },
    { id: 'layout', label: 'layout' },
    { id: 'navigation', label: 'navigation' },
    { id: 'overlay', label: 'overlay' },
    { id: 'action', label: 'action' },
    { id: 'data', label: 'data' },
    { id: 'stable', label: 'stable', intent: 'success' },
    { id: 'beta', label: 'beta', intent: 'warning' },
    { id: 'experimental', label: 'experimental', intent: 'neutral' }
  ];
  const links = [
    { source: 'blocks', target: 'form', value: 17 },
    { source: 'blocks', target: 'display', value: 15 },
    { source: 'blocks', target: 'ai', value: 10 },
    { source: 'blocks', target: 'layout', value: 10 },
    { source: 'blocks', target: 'action', value: 8 },
    { source: 'blocks', target: 'navigation', value: 8 },
    { source: 'blocks', target: 'overlay', value: 8 },
    { source: 'blocks', target: 'feedback', value: 7 },
    { source: 'auth', target: 'form', value: 8 },
    { source: 'auth', target: 'feedback', value: 4 },
    { source: 'auth', target: 'display', value: 1 },
    { source: 'auth', target: 'data', value: 1 },
    { source: 'table', target: 'data', value: 1 },
    { source: 'form', target: 'stable', value: 20 },
    { source: 'form', target: 'beta', value: 5 },
    { source: 'display', target: 'beta', value: 11 },
    { source: 'display', target: 'stable', value: 5 },
    { source: 'ai', target: 'experimental', value: 10 },
    { source: 'layout', target: 'stable', value: 7 },
    { source: 'layout', target: 'experimental', value: 2 },
    { source: 'layout', target: 'beta', value: 1 },
    { source: 'action', target: 'stable', value: 6 },
    { source: 'action', target: 'beta', value: 2 },
    { source: 'navigation', target: 'stable', value: 5 },
    { source: 'navigation', target: 'beta', value: 3 },
    { source: 'overlay', target: 'stable', value: 4 },
    { source: 'overlay', target: 'beta', value: 4 },
    { source: 'feedback', target: 'stable', value: 10 },
    { source: 'feedback', target: 'beta', value: 1 },
    { source: 'data', target: 'stable', value: 2 }
  ];
</script>

<Sankey
  {nodes}
  {links}
  height={440}
  nodePadding={12}
/>

01 Examples

5-Stage Heating Cost Flow

Complete service-charge example: sources (gas, heat pump, maintenance, chimney sweep) → heating cost pot → heating/hot water → residential units.
Gas billHeat pump electricityMaintenanceChimney sweepHeating cost potHeatingHot waterUnit 4Unit 4A
Sankey diagram: 9 nodes, 10 links — Gas bill → Heating cost pot: 2.206,09 €; Heat pump electricity → Heating cost pot: 1.127,31 €; Maintenance → Heating cost pot: 361,02 €; Chimney sweep → Heating cost pot: 128,00 €; Heating cost pot → Heating: 3.105,03 €; Heating cost pot → Hot water: 717,39 €; Heating → Unit 4: 1.855,47 €; Heating → Unit 4A: 1.249,56 €; Hot water → Unit 4: 394,77 €; Hot water → Unit 4A: 322,62 €
SourceTargetValue
Gas billHeating cost pot2.206,09 €
Heat pump electricityHeating cost pot1.127,31 €
MaintenanceHeating cost pot361,02 €
Chimney sweepHeating cost pot128,00 €
Heating cost potHeating3.105,03 €
Heating cost potHot water717,39 €
HeatingUnit 41.855,47 €
HeatingUnit 4A1.249,56 €
Hot waterUnit 4394,77 €
Hot waterUnit 4A322,62 €
<div class="w-full max-w-4xl">
  <Sankey nodes={heatingNodes} links={heatingLinks} formatValue={formatEur} height={500} />
</div>

Salary Breakdown

Gross salary → income tax / social contributions / net pay. Social contributions branch further into health, pension, unemployment, and care insurance.
Gross salaryIncome taxSocial contributionsHealth insurancePension insuranceUnemployment ins.Long-term careNet pay
Sankey diagram: 8 nodes, 7 links — Gross salary → Income tax: 850 €; Gross salary → Social contributions: 1.080 €; Social contributions → Health insurance: 360 €; Social contributions → Pension insurance: 480 €; Social contributions → Unemployment ins.: 90 €; Social contributions → Long-term care: 150 €; Gross salary → Net pay: 2.570 €
SourceTargetValue
Gross salaryIncome tax850 €
Gross salarySocial contributions1.080 €
Social contributionsHealth insurance360 €
Social contributionsPension insurance480 €
Social contributionsUnemployment ins.90 €
Social contributionsLong-term care150 €
Gross salaryNet pay2.570 €
<div class="w-full max-w-3xl">
  <Sankey
    nodes={salaryNodes}
    links={salaryLinks}
    formatValue={(v) => `${v.toLocaleString('de-DE')}`}
    height={400}
  />
</div>

Node Alignments

Four layer alignments. The early sink (B) lands in different positions depending on alignment — justify pushes it all the way right, left keeps it at its source layer.

justify

SourceMidAB (early sink)
Sankey diagram: 4 nodes, 3 links — Source → Mid: 60; Mid → A: 60; Source → B (early sink): 30
SourceTargetValue
SourceMid60
MidA60
SourceB (early sink)30

left

SourceMidAB (early sink)
Sankey diagram: 4 nodes, 3 links — Source → Mid: 60; Mid → A: 60; Source → B (early sink): 30
SourceTargetValue
SourceMid60
MidA60
SourceB (early sink)30

right

SourceMidAB (early sink)
Sankey diagram: 4 nodes, 3 links — Source → Mid: 60; Mid → A: 60; Source → B (early sink): 30
SourceTargetValue
SourceMid60
MidA60
SourceB (early sink)30

center

SourceMidAB (early sink)
Sankey diagram: 4 nodes, 3 links — Source → Mid: 60; Mid → A: 60; Source → B (early sink): 30
SourceTargetValue
SourceMid60
MidA60
SourceB (early sink)30
{#each ['justify', 'left', 'right', 'center'] as align (align)}
  <div class="border-border-subtle bg-surface-elevated rounded-contain border p-3">
    <h4 class="text-text-secondary mb-2 text-xs font-medium tracking-wide uppercase">
      {align}
    </h4>
    <Sankey
      nodes={alignmentNodes}
      links={alignmentLinks}
      formatValue={(v) => `${v}`}
      height={180}
      nodeAlign={align as 'justify' | 'left' | 'right' | 'center'}
    />
  </div>
{/each}

Compact Embedding

A small height embedded in a card — the layout adapts automatically.

Cashflow Overview

Q1 2026

SourceTarget
Sankey diagram: 2 nodes, 1 links — Source → Target: 100 %
SourceTargetValue
SourceTarget100 %
<div class="border-border-subtle bg-surface-elevated w-full max-w-md rounded-2xl border p-5">
  <header class="mb-3">
    <h3 class="text-text-primary text-sm font-semibold">Cashflow Overview</h3>
    <p class="text-text-tertiary text-xs">Q1 2026</p>
  </header>
  <Sankey
    nodes={minimalNodes}
    links={minimalLinks}
    formatValue={(v) => `${v} %`}
    height={140}
    nodeWidth={16}
    nodePadding={10}
  />
</div>

02 Customization

Custom Tooltip

Custom tooltip content with a conditionally formatted detail field.
Gas billHeat pump electricityMaintenanceChimney sweepHeating cost potHeatingHot waterUnit 4Unit 4A
Sankey diagram: 9 nodes, 10 links — Gas bill → Heating cost pot: 2.206,09 €; Heat pump electricity → Heating cost pot: 1.127,31 €; Maintenance → Heating cost pot: 361,02 €; Chimney sweep → Heating cost pot: 128,00 €; Heating cost pot → Heating: 3.105,03 €; Heating cost pot → Hot water: 717,39 €; Heating → Unit 4: 1.855,47 €; Heating → Unit 4A: 1.249,56 €; Hot water → Unit 4: 394,77 €; Hot water → Unit 4A: 322,62 €
SourceTargetValue
Gas billHeating cost pot2.206,09 €
Heat pump electricityHeating cost pot1.127,31 €
MaintenanceHeating cost pot361,02 €
Chimney sweepHeating cost pot128,00 €
Heating cost potHeating3.105,03 €
Heating cost potHot water717,39 €
HeatingUnit 41.855,47 €
HeatingUnit 4A1.249,56 €
Hot waterUnit 4394,77 €
Hot waterUnit 4A322,62 €
<div class="w-full max-w-3xl">
  <Sankey nodes={heatingNodes} links={heatingLinks} formatValue={formatEur} height={400}>
    {#snippet tooltip(datum, kind)}
      {#if kind === 'node'}
        {@const node = datum as { id: string; value: number }}
        <span class="block font-medium">{node.id}</span>
        <span class="text-text-tertiary text-2xs block tabular-nums">
          {formatEur(node.value)}
        </span>
      {:else}
        {@const link = datum as {
          source: { id: string };
          target: { id: string };
          value: number;
        }}
        <span class="block font-medium">
          {link.source.id}{link.target.id}
        </span>
        <span class="text-primary text-2xs block font-semibold tabular-nums">
          {formatEur(link.value)}
        </span>
      {/if}
    {/snippet}
  </Sankey>
</div>

03 Accessibility

SVG with role="img"

The SVG has role="img" with an aria-label that lists every connection ("Source → Target: value"). A screen reader announces the diagram in a single block.

Table Fallback

An sr-only table (source / target / value) is rendered in addition — screen readers that prefer tables can query the data row by row.

Keyboard Navigation

Tab focuses every node and link in sequence. On a focused element, Enter/Space triggers the click handler (onNodeClick / onLinkClick).

Hover + Focus Highlight

Hovering or focusing a node dims all unconnected paths and nodes and highlights its direct connections.

04 API Reference

27 props
27 props 2 required
Prop
Type
Default
Description

05 Types

Local type definitions used by this component.

10 types
Name
Kind
Category
Used by
Description

06 Installation

Import

import { Sankey } from '@urbicon-ui/blocks';