Diagram Kit
Eleven data-driven SVG diagrams — hub routing, sequence lanes, cycle, timeline, radial branches, funnel, tier pyramid, anchored callouts, branch reveal, quadrant and Venn — each drawn in with stroke tracing and staggered nodes.
This is a WebberUI Pro component
The install command below needs a licence to run; without one it returns 401.
The eleven diagrams that show up most in decks, explainer pages and product tours, all described as data: you pass nodes and relationships, the component handles layout and the entrance. They share one animation contract — links and outlines are traced in as strokes, nodes pop in following the topology, and hovering or focusing a node keeps only its related paths lit while the rest fade, with a tooltip for extra detail. The DiagramKit frame provides the title, description, legend and play / replay control, and each diagram is reached as a static property such as DiagramKit.Venn. Every diagram is an SVG whose viewBox scales to the container width, and CJK labels wrap automatically.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add "https://webberui.com/r/diagram-kit.en.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<DiagramKit/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add "https://webberui.com/r/diagram-kit.en.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/diagram-kit.
Usage
import { DiagramKit } from "@/components/ui/diagram-kit";
<DiagramKit title="How the order hub routes work" legend={[{ label: "Inbound" }, { label: "Outbound" }]}>
<DiagramKit.HubRouting
hub={{ id: "hub", label: "Order hub", detail: "About 1,200 orders a day" }}
spokes={[
{ id: "web", label: "Website", side: "in" },
{ id: "store", label: "Store", side: "in" },
{ id: "wh", label: "Warehouse", side: "out" },
{ id: "cs", label: "Support", side: "out" },
]}
/>
</DiagramKit>;
<DiagramKit trigger="mount" accent="#0ea5e9">
<DiagramKit.Venn
sets={[
{ id: "design", label: "Design" },
{ id: "eng", label: "Engineering" },
{ id: "biz", label: "Business" },
]}
intersections={[
{ sets: ["design", "eng", "biz"], words: ["Usable", "Buildable", "Profitable"] },
]}
/>
</DiagramKit>;Each diagram also works on its own without the DiagramKit frame; it then plays as soon as it mounts.
Props
DiagramKit
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Diagram title |
description | string | — | Description under the title |
legend | DiagramLegendItem[] | — | Legend items; without a color they take the series palette in order |
playing | boolean | — | Controlled play state (true means the entrance has been triggered) |
defaultPlaying | boolean | false | Initial play state in uncontrolled mode |
onPlayingChange | (playing: boolean) => void | — | Called when the play state changes |
trigger | "inView" | "mount" | "manual" | "inView" | Entrance trigger: 25% scrolled into view, on mount, or only via the button / playing |
drawDuration | number | 700 | Duration of one stroke trace (ms); falls back to --wb-diagram-draw |
stagger | number | 90 | Delay between nodes entering (ms); falls back to --wb-diagram-stagger |
accent | string | #3b82f6 | Accent color, written to --wb-diagram-accent |
series | string[] | accent + 4 colors | Custom palette; the first color is the main one |
controls | boolean | true | Show the play / replay button |
children | ReactNode | — | One diagram |
className | string | — | Passed to the outermost container |
Every diagram also takes interactive?: boolean (default true: hover / focus highlighting and tooltips), label?: string (the SVG's accessible name) and className?: string. The tables below list only each diagram's data props; the shared node type DiagramNode is { id, label, detail? }.
DiagramKit.HubRouting
| Field | Type | Default | Description |
|---|---|---|---|
hub | DiagramNode | — | The central hub |
spokes | HubSpoke[] | — | Spoke nodes; side: "in" feeds in from the left, "out" routes out to the right |
pulse | boolean | true | Pulses travelling along the links once the entrance finishes |
DiagramKit.SequenceLanes
| Field | Type | Default | Description |
|---|---|---|---|
lanes | DiagramNode[] | — | Lanes (participants), left to right |
messages | SequenceMessage[] | — | Messages in time order { from, to, label, detail? }; when from equals to a self-loop is drawn |
DiagramKit.CycleBuild
| Field | Type | Default | Description |
|---|---|---|---|
steps | DiagramNode[] | — | Steps in cycle order; the last one links back to the first |
center | string | — | Title in the middle of the ring |
DiagramKit.TimelineNodes
| Field | Type | Default | Description |
|---|---|---|---|
events | TimelineEvent[] | — | { id?, date, title, detail? }, in chronological order |
orientation | "auto" | "horizontal" | "vertical" | "auto" | auto switches to vertical when the container is narrower than 480px |
DiagramKit.RadialBranches
| Field | Type | Default | Description |
|---|---|---|---|
center | DiagramNode | — | Center node |
branches | RadialBranch[] | — | First-level branches (with children as the second level), clockwise from the top |
DiagramKit.FunnelAbsorption
| Field | Type | Default | Description |
|---|---|---|---|
stages | FunnelStage[] | — | { id?, label, value, detail? }, top to bottom |
particles | boolean | true | Particles drawn into the funnel and flowing down after the entrance |
valueFormatter | (value: number) => string | thousands separators | Value formatter |
DiagramKit.TierPyramid
| Field | Type | Default | Description |
|---|---|---|---|
tiers | DiagramNode[] | — | Tiers; tiers[0] is the base and the last item is the apex |
DiagramKit.AnchoredCallouts
| Field | Type | Default | Description |
|---|---|---|---|
anchors | CalloutAnchor[] | — | { id, x, y, label, detail?, side? }; x and y are percentages 0–100 of the underlying content |
children | ReactNode | — | The underlying content (a screenshot, card or illustration) |
DiagramKit.BranchReveal
| Field | Type | Default | Description |
|---|---|---|---|
root | BranchNode | — | Tree root; children holds child nodes |
expandedIds | string[] | — | Controlled expanded node ids |
defaultExpandedIds | string[] | [root.id] | Initially expanded nodes in uncontrolled mode |
onExpandedChange | (ids: string[]) => void | — | Called when the expanded set changes |
With interactive={false} the whole tree is expanded and cannot be toggled.
DiagramKit.QuadrantPositioning
| Field | Type | Default | Description |
|---|---|---|---|
xAxis | QuadrantAxis | — | Labels for both ends of the x axis { low, high } |
yAxis | QuadrantAxis | — | Labels for both ends of the y axis { low, high } |
quadrants | [string, string, string, string] | — | Quadrant names in the order top-left, top-right, bottom-left, bottom-right |
items | QuadrantItem[] | — | Controlled items { id, label, x, y, detail? }, x and y in 0–100 |
defaultItems | QuadrantItem[] | [] | Initial items in uncontrolled mode |
onItemsChange | (items: QuadrantItem[]) => void | — | Called after an item is dragged or moved with the arrow keys |
DiagramKit.Venn
| Field | Type | Default | Description |
|---|---|---|---|
sets | VennSet[] | — | 2 or 3 sets (only the first three are used) |
intersections | VennIntersection[] | [] | { sets, label?, words?, detail? }; with more than one entry in words they rotate |
wordInterval | number | 1800 | Interval between intersection word swaps (ms) |
Also exported by name: wrapText (CJK-aware line-wrap estimation), DIAGRAM_SERIES and all of the types above.
How it works
- Shared animation contract: link and outline
pathLengthgoes from 0 to 1 (easing[0.65, 0, 0.35, 1]), and nodes scale up from 0.6 with a fade on a spring (stiffness 320, damping 26), entering in topological orderstaggerapart - Timing variables:
--wb-diagram-draw(700ms),--wb-diagram-stagger(90ms),--wb-diagram-accent(#3b82f6) and--wb-diagram-dim(0.25, the opacity of everything else while highlighting); props take precedence over the variables - Replay: the frame's Replay button remounts the diagram so the whole entrance runs again from the start
- CJK wrapping: labels live in SVG
textand wrap by estimated character width (CJK can break between any two characters, Latin text breaks between words, and punctuation is kept off the start of a line); text beyond the line limit ends in an ellipsis - Venn: the dashed outlines are traced in, then the circles spring together from apart, and the overlaps light up via clip-path fills; intersection words cross-fade every 1.8 seconds
- Funnel: particles with a deterministic spread are drawn in from the top and some are "absorbed" at intermediate stages, matching each stage's drop-off; each stage shows its conversion rate from the previous one on the right
- Anchored callouts: the underlying content can be anything; the component measures where it sits, splits labels between the left and right sides without overlapping, and connects each one to its anchor with an elbow leader line
Accessibility
- Each diagram's SVG has a
titleanddesc, plus ansr-onlystructured text alternative (a list or table) so screen readers get the full data - Nodes are focusable with Tab, and focusing one highlights its related paths just like hover; keyboard focus also draws an outline
- Branch reveal nodes are
role="button"witharia-expanded, toggled with Enter or Space (Enter during IME composition is ignored) - Quadrant items can be dragged, or focused and moved with the arrow keys (hold Shift to move by 10)
- Tooltips are purely visual (
aria-hidden); the same information is in thesr-onlytext - When the user has turned on reduce motion in their system settings, every diagram shows its final state straight away: no stroke tracing, no node pop-ins, no particles or pulses, and the Venn intersection words do not rotate
Duration Compare Bars
A duration comparison React component: two to five durations grow on one shared scale at one shared speed — say 30 minutes versus six weeks. Units convert automatically, readouts count up with the bar width, a ratio badge pops in when they finish, and a broken axis keeps the shortest bar visible when the gap is extreme.
Balance Chart
A diverging bar chart React component centred on a zero axis: income grows up and expenses grow down, a net line draws in once the bars land, and hover or keyboard focus shows the period's income, expenses and net.