A pannable, zoomable tidy-tree diagram of any hierarchy: org charts, partner networks, decision-maker maps, site structure, branch trees. Collapsible subtrees, search highlight, focus-to-node, drag-to-reparent with a Change parent dialog as the non-drag path, viewport culling for thousands of nodes, SVG and PNG export, and a list view over the same data.
Arrow keys pan the canvas, plus and minus zoom, 0 fits the chart. In the tree, arrow keys move between nodes, Shift+F10 opens node actions.
Selected: Marcus Lee, Chief Technology Officer
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { HierarchyChart, HierarchyPersonNode } from "@vamos-ui/react";
// Flat rows: { id, parentId } — parentId null for the root.
<HierarchyChart
aria-label="Organization"
nodes={people}
getLabel={(p) => p.name}
getDescription={(p) => p.title}
renderNode={(p) => <HierarchyPersonNode name={p.name} title={p.title} src={p.photo} />}
onReparent={(e) => movePerson(e.id, e.newParentId)}
/>Accessibility follows the Tree view pattern on the nodes: role="tree" with levels, positions and expanded state. ↑ / ↓ go through nodes in reading order, → opens or enters, ← closes or goes to the parent, Home / End, * opens siblings, Enter or Space selects, type-ahead by first letter. The focused node is panned into view.
The canvas itself is focusable: arrow keys pan (Shift for bigger steps), + / − zoom, 0 fits. Shift+F10 on a node opens its menu (expand, center, Change parent…).
Nodes never blur; the controls are the one floating surface. Blur turns off while dragging. Reduced motion turns off animated pan and zoom.
Canvas coordinates are physical; in RTL the layout itself is mirrored so the root and the first sibling sit on the inline-start side.
Examples
Partner network
PRM: distributor → reseller → sub-reseller, left to right, with tier badges and yearly volume. In RTL the root sits on the right.
Arrow keys pan the canvas, plus and minus zoom, 0 fits the chart. In the tree, arrow keys move between nodes, Shift+F10 opens node actions.
Drag to reparent
A CRM decision-maker map. Drag a contact onto another to change who they report to; cycles and canReparent reasons turn the target red. The node menu's Change parent… opens a searchable picker. An Undo toast restores the previous map.
Arrow keys pan the canvas, plus and minus zoom, 0 fits the chart. In the tree, arrow keys move between nodes, Shift+F10 opens node actions.
Drag a person onto their new manager, or open a node's menu (Shift+F10) and choose Change parent.
Search and list view
A restaurant site structure. highlight marks matching nodes (locale-folded) and opens their ancestors; view="list" renders the same data with Tree view.
Arrow keys pan the canvas, plus and minus zoom, 0 fits the chart. In the tree, arrow keys move between nodes, Shift+F10 opens node actions.
2,000 nodes
Large trees render only the nodes near the viewport. Here defaultCollapsed={{ depth: 1 }} starts at the directors (without it, trees beyond 500 nodes start collapsed below two levels). focusNode() opens the ancestors and pans to a person.
Arrow keys pan the canvas, plus and minus zoom, 0 fits the chart. In the tree, arrow keys move between nodes, Shift+F10 opens node actions.
Export SVG / PNG
Branch structure of a multi-branch business. toSVG() returns a standalone SVG with the theme's colours resolved; toPNG(scale) renders it to a Blob.
Arrow keys pan the canvas, plus and minus zoom, 0 fits the chart. In the tree, arrow keys move between nodes, Shift+F10 opens node actions.
API
HierarchyChart
| Prop | Type | Default |
|---|---|---|
nodesFlat rows. Several roots are allowed; unknown parents become roots. | T[] (T extends { id, parentId }) | — |
renderNodeContent inside the kit's node frame (selection ring, match tint, menu, collapse badge are added for you). | (node, { depth, childCount, descendantCount, collapsed, selected, matched, orientation }) => ReactNode | HierarchyNodeCard |
getLabel / getDescriptionText for type-ahead, search, export, the parent picker and the list view. | (node) => string | name / label / title |
isPlaceholderVacant / placeholder nodes get a dashed frame. | (node) => boolean | — |
orientation / defaultOrientation / onOrientationChangeTop-to-bottom or start-to-end. The controls toggle it. | "vertical" | "horizontal" | "vertical" |
nodeSizeNode box in px: compact 184 × 56, card 236 × 84. | { width, height } | "compact" | "card" | "card" |
gapSpace between siblings and between levels. | { x, y } | { x: 28, y: 56 } |
collapsed / defaultCollapsed / onCollapsedChangeCollapsed nodes hide their subtree and show +N on the badge. | string[] / string[] | { depth } / (ids) => void | { depth: 2 } beyond 500 nodes |
selected / defaultSelected / onSelectedChangeClick, Enter or Space selects. The path to the root is drawn in accent. | string | null | — |
focusIdPans and zooms to this node when it changes (opens ancestors). | string | null | — |
highlightSearch text. Matching nodes get the accent tint; their ancestors open. | string | — |
onReparentEnables drag-to-reparent and Change parent…. A promise keeps the move optimistic; a rejection rolls back with Retry. | (e: { id, newParentId, oldParentId }) => void | Promise<void> | — |
canReparentA string blocks the move with that reason. Cycles are always blocked. | (e) => true | string | — |
nodeMenuExtra DropdownMenuItems in each node's action menu. | (node) => ReactNode | — |
viewlist renders the same data as a Tree view. | "chart" | "list" | "chart" |
controlsFloating zoom, fit, orientation, expand all / collapse all. | boolean | true |
heightCanvas height. | number | string | "32rem" |
loading / emptySkeleton tree, or the empty message. | boolean / ReactNode | — |
labelsBuilt-in strings. Dictionary namespace: hierarchyChart. | Partial<HierarchyChartLabels> | — |
reffit(), zoomIn(), zoomOut(), focusNode(id), expandAll(), collapseAll(), toSVG(): string, toPNG(scale?): Promise<Blob>. | HierarchyChartHandle | — |
HierarchyPersonNode
| Prop | Type | Default |
|---|---|---|
name / title / srcAvatar (initials fallback), name and job title. | string / string / string | null | — |
meta / badgeBottom line (team size, location) and a badge after the name. | ReactNode | — |
vacantOpen position: dashed avatar, muted name. Pair with isPlaceholder. | boolean | — |
HierarchyNodeCard
| Prop | Type | Default |
|---|---|---|
title / subtitle / metaThree lines of text. | ReactNode | — |
icon / badgeLeading visual and trailing badge (tier, status). | ReactNode | — |
layoutHierarchy(nodes, options)
| Prop | Type | Default |
|---|---|---|
returnsThe pure tidy-tree layout, for server-side rendering or custom drawings. | { nodes: { id, x, y, depth, parentId, … }[], width, height } | — |