Skip to content

Organization Chart

Data visualizationSince 0.5.0
import { OrgChart, type OrgChartNode } from '@absoluteui/components/org-chart';
import '@absoluteui/components/org-chart/style.css';

Using Angular? See Organization Chart for Angular.

Shows who reports to whom. OrgChart renders flat nodes (id, parentId) as a tidy tree, top-down or left-to-right, with elbow connectors and cards (initials, name, title, description, or custom content). The nodes form an ARIA tree with one tab stop and tree keyboard navigation; moving focus pans the view so the node stays visible.

Users pan by dragging the background and zoom with the toolbar, +/-/0, or Ctrl/Cmd + wheel; fit() and centerOn() frame the chart. Reports load on demand (loadChildren) with cancellation and a timeout. search(), pathTo(), and reveal() find a person, expand their managers, and highlight the path. In edit mode people are moved to a new manager by pointer drag, by Ctrl/Cmd+X then Ctrl/Cmd+V, or with the toolbar Move / Move here buttons; canMove vetoes moves, locked nodes stay, and moves are undoable. layoutOrgChart (the layout) is a pure function.

  • Showing who reports to whom, from a few people to a few thousand visible nodes.
  • Letting HR or managers restructure teams by moving people to new managers (edit mode, undo).
  • Browsing a large organization by loading teams on demand (loadChildren) and finding people (search, reveal).
  • Deep hierarchies that users scan as a list (folders, categories): use a tree view or Tree Grid pattern.
  • Networks where a node has several parents (matrix organizations, graphs).
  • Hierarchical part-to-whole values: use Treemap.
import { OrgChart, type OrgChartNode, type OrgChartNodeSelectDetail } from '@absoluteui/components/org-chart';
const nodes: OrgChartNode[] = [
{ id: 'ceo', parentId: null, label: 'Ada Lovelace', title: 'Chief Executive Officer' },
{ id: 'cto', parentId: 'ceo', label: 'Grace Hopper', title: 'Chief Technology Officer' },
{ id: 'cfo', parentId: 'ceo', label: 'Mary Jackson', title: 'Chief Financial Officer' },
{ id: 'eng', parentId: 'cto', label: 'Margaret Hamilton', title: 'Engineering Lead', description: 'Boston' },
];
const host = document.createElement('div');
document.body.append(host);
const chart = OrgChart.create(host, { label: 'Company structure', nodes });
host.addEventListener('aui-node-select', (event) => {
const { node } = (event as CustomEvent<OrgChartNodeSelectDetail>).detail;
host.dataset.selected = node.label;
});
chart.fit();
import { OrgChart, type OrgChartLoadErrorDetail, type OrgChartNode } from '@absoluteui/components/org-chart';
import { TextInput } from '@absoluteui/components/text-input';
const top: OrgChartNode[] = [
{ id: 'ceo', parentId: null, label: 'Ada Lovelace', title: 'CEO' },
{ id: 'sales', parentId: 'ceo', label: 'Sales', title: 'Division', hasChildren: true },
{ id: 'ops', parentId: 'ceo', label: 'Operations', title: 'Division', hasChildren: true },
];
const host = document.querySelector<HTMLElement>('#org');
if (host) {
const chart = OrgChart.create(host, {
label: 'Organization',
nodes: top,
orientation: 'horizontal',
requestTimeoutMs: 10_000,
loadChildren: async (node, signal) => {
const response = await fetch(`/api/people?manager=${encodeURIComponent(node.id)}`, { signal });
if (!response.ok) throw new Error(`Team request failed: ${String(response.status)}`);
return (await response.json()) as OrgChartNode[];
},
});
host.addEventListener('aui-load-error', (event) => {
host.dataset.failed = (event as CustomEvent<OrgChartLoadErrorDetail>).detail.id;
});
const searchHost = document.createElement('div');
const search = TextInput.create(searchHost, { label: 'Find a person', hideLabel: true, type: 'search' }).input;
search.addEventListener('change', () => {
const first = chart.search(search.value)[0];
if (first) chart.reveal(first, { select: true });
});
host.before(searchHost);
}

Restructure teams with undo, and save the result

Section titled “Restructure teams with undo, and save the result”
import { OrgChart, type OrgChartNode, type OrgChartNodeMoveDetail } from '@absoluteui/components/org-chart';
import { Button } from '@absoluteui/components/button';
let saved: OrgChartNode[] = [
{ id: 'lead', parentId: null, label: 'Team Lead', locked: true },
{ id: 'a', parentId: 'lead', label: 'Alex Kim', title: 'Designer' },
{ id: 'b', parentId: 'lead', label: 'Sam Lee', title: 'Developer' },
{ id: 'c', parentId: 'b', label: 'Robin Park', title: 'Intern' },
];
const host = document.createElement('div');
document.body.append(host);
const chart = OrgChart.create(host, {
label: 'Team',
nodes: saved,
editable: true,
// Interns stay with developers.
canMove: (node, parent) => node.title !== 'Intern' || parent?.title === 'Developer',
});
host.addEventListener('aui-node-move', (event) => {
saved = (event as CustomEvent<OrgChartNodeMoveDetail>).detail.nodes;
});
const undo = document.createElement('button');
undo.type = 'button';
undo.textContent = 'Undo last move';
Button.enhance(undo);
undo.addEventListener('click', () => {
chart.undo();
});
host.after(undo);
import { OrgChart } from '@absoluteui/components/org-chart';
import { downloadOrgChart } from '@absoluteui/components/org-chart-export';
import { Button } from '@absoluteui/components/button';
const host = document.createElement('div');
document.body.append(host);
const chart = OrgChart.create(host, {
label: 'Board',
nodes: [
{ id: 'chair', parentId: null, label: 'Chair', title: 'Jordan Blake' },
{ id: 'audit', parentId: 'chair', label: 'Audit committee', title: '3 members' },
],
nodeWidth: 240,
nodeHeight: 64,
// Non-interactive content only: the card is part of the treeitem.
renderNode: (node, { doc, childCount }) => {
const box = doc.createElement('span');
const name = doc.createElement('strong');
name.textContent = node.label;
const meta = doc.createElement('span');
meta.textContent = `, ${node.title ?? ''}${childCount > 0 ? `, ${String(childCount)} reports` : ''}`;
box.append(name, meta);
return box;
},
});
const save = document.createElement('button');
save.type = 'button';
save.textContent = 'Download PNG';
Button.enhance(save);
save.addEventListener('click', () => {
void downloadOrgChart(chart, { format: 'png', fileName: 'board' });
});
host.after(save);
import { layoutOrgChart } from '@absoluteui/components/org-chart';
const layout = layoutOrgChart(
[{ id: 'a', children: [{ id: 'b', children: [] }, { id: 'c', children: [] }] }],
{ nodeWidth: 120, nodeHeight: 40, siblingGap: 16, levelGap: 32, orientation: 'vertical' },
);
const root = layout.boxes.get('a');
document.body.dataset.size = `${String(layout.width)}x${String(layout.height)} root at ${String(root?.x ?? 0)}`;

Left-to-right charts with smaller cards everywhere

Section titled “Left-to-right charts with smaller cards everywhere”
import { configureAbsoluteUI } from '@absoluteui/core';
configureAbsoluteUI({ components: { orgChart: { orientation: 'horizontal', nodeWidth: 180, nodeHeight: 56, expandDepth: 1 } } });
import { OrgChart, type OrgChartNode, type OrgChartNodeMoveDetail } from '@absoluteui/components/org-chart';
import '@absoluteui/components/org-chart/style.css';
const nodes: OrgChartNode[] = [
{ id: 'ceo', parentId: null, label: 'Ada Lovelace', title: 'CEO' },
{ id: 'cto', parentId: 'ceo', label: 'Grace Hopper', title: 'CTO' },
{ id: 'dev', parentId: 'cto', label: 'Margaret Hamilton', title: 'Engineer' },
];
const host = document.querySelector<HTMLElement>('#org');
if (host) {
const chart = OrgChart.create(host, { label: 'Company', nodes, editable: true });
host.addEventListener('aui-node-move', (event) => {
const { nodes: updated } = (event as CustomEvent<OrgChartNodeMoveDetail>).detail;
sessionStorage.setItem('org', JSON.stringify(updated));
});
chart.reveal('dev', { focus: false });
}

See section 33 and metadata/org-chart/examples.json: basic chart, lazy teams with search, editing with undo, custom cards with export, the pure layout, global configuration.

<div class="aui-org-chart [--editing] [--horizontal]"> (consumer host)
<div class="aui-org-chart__toolbar" role="group" aria-label="{label} controls">
<button class="aui-org-chart__tool" data-aui-action="zoomOut|zoomIn|fit|cut|paste|undo|redo" aria-label="..." [aria-disabled] [aria-pressed]>
<div class="aui-org-chart__viewport"> (clips; background drags pan)
<div class="aui-org-chart__canvas" style="transform: translate(…) scale(…)"> (CSSOM)
<svg class="aui-org-chart__links" aria-hidden="true"><path class="aui-org-chart__link [--path]">
<div class="aui-org-chart__tree" role="tree" aria-label="{label}">
<div class="aui-org-chart__node [--path] [--cut] [--locked]" role="treeitem" data-aui-node="{id}"
aria-level="1" aria-setsize="1" aria-posinset="1" aria-expanded="true" aria-selected="false" tabindex="0">
<div class="aui-org-chart__card">
<span class="aui-org-chart__avatar" aria-hidden="true">AL</span>
<span class="aui-org-chart__text"><span class="…__name">Ada Lovelace</span><span class="…__title"><span class="…__sep">, </span>CEO</span>
<span class="aui-org-chart__toggle" aria-hidden="true">3 ⌄</span> (pointer expand chip)
OrgChart.create(host: HTMLElement, options: OrgChartOptions): OrgChart; layoutOrgChart(roots: readonly OrgChartLayoutInput[], options: OrgChartLayoutOptions): OrgChartLayout. Export subpath @absoluteui/components/org-chart-export: orgChartToSvg(chart: OrgChart, options?: OrgChartExportOptions): string; orgChartToPng(chart, options?): Promise<Blob>; downloadOrgChart(chart, options: OrgChartDownloadOptions): Promise<void>

Global configuration key: orgChart (see Configuration).

Option Type Default Description
label string Accessible name of the tree (aria-label); the toolbar is named ‘{label} controls’. Non-empty. Required.
nodes readonly OrgChartNode[] Flat nodes: { id (unique), parentId (manager id or null for a top-level node), label (name), title?, description?, hasChildren? (reports exist but load through loadChildren), locked? (cannot be moved in edit mode) }. Validated: unique ids, known parents, no cycles (ORG_CHART_OPTIONS). Sibling order follows the array. Replacing it with update() clears the undo history. Required.
expanded readonly string[] Explicitly expanded node ids (overrides expandDepth). Read the current set from the expanded getter.
selected string | null Selected node id (aria-selected=true).
renderNode (node: Readonly<OrgChartNode>, context: OrgChartRenderContext) => Node Card content ({ depth, expanded, childCount, doc } context). Return non-interactive content: the treeitem is the control and its text is its accessible name. Default: initials, label, title, description.
loadChildren (node: Readonly<OrgChartNode>, signal: AbortSignal) => Promise<readonly OrgChartNode[]> Loads the reports of a node with hasChildren on its first expansion (validated; each parentId must be a known or returned node). Rejections and timeouts keep the node collapsed, announce the failure, and emit aui-load-error.
editable boolean false Edit mode: re-parent nodes by pointer drag onto the new manager, Ctrl/Cmd+X then Ctrl/Cmd+V, or the toolbar Move / Move here buttons; undo and redo.
canMove (node: Readonly<OrgChartNode>, parent: Readonly<OrgChartNode> | null) => boolean Veto a move (edit mode and moveNode). Cycles and locked nodes are always refused.
historyLimit number 50 Undo depth, integer 0..500 (0 disables history).
orientation 'vertical' | 'horizontal' "vertical" ‘vertical’: managers above their reports (top-down); ‘horizontal’: managers at the inline start, reports toward the inline end. Globally configurable.
nodeWidth number 200 Card width in CSS px, integer 80..600. Globally configurable.
nodeHeight number 72 Card height in CSS px, integer 32..400. Globally configurable.
siblingGap number 24 Space between neighboring cards of a level, integer 4..400 CSS px. Globally configurable.
levelGap number 48 Space between levels (connector length), integer 16..400 CSS px. Globally configurable.
expandDepth number 2 Levels expanded initially when expanded is not given (integer 0..64; 0 shows only the top-level nodes). Globally configurable.
minZoom number 0.2 Smallest zoom factor, 0.05..1. Globally configurable.
maxZoom number 2 Largest zoom factor, 1..8. Globally configurable.
requestTimeoutMs number 30000 loadChildren timeout in ms (integer 100..600000); the signal is aborted with a TimeoutError. Globally configurable.
locale string "en-US" Locale of search, typeahead, report counts, zoom percentages, and messages. Globally configurable. Inherited from common configuration.
direction 'ltr' | 'rtl' "ltr" rtl mirrors the layout (first report on the right; horizontal charts grow to the left) and swaps ArrowLeft/ArrowRight. Globally configurable. Inherited from common configuration.
Property Type Description
host HTMLElement The host (class aui-org-chart).
element HTMLDivElement The tree element (role=tree).
nodes OrgChartNode[] Every node with its current manager, in tree order (copies, for saving after edits and lazy loads).
expanded string[] Expanded node ids.
selected string | null Selected node id.
editable boolean Edit mode.
scale number Current zoom factor.
canUndo | canRedo boolean Move history is available.
geometry OrgChartGeometry Visible layout (boxes, edges, size), nodes, a rendered card for computed colors, and the highlighted path (used by org-chart-export).
Method Description
update(options: Partial<OrgChartOptions>): void Merge and apply options (validated first). nodes replaces the data (undo history cleared, expansion kept for ids that still exist); expanded or expandDepth resets the expansion. No event.
setExpanded(id: string, expanded: boolean): Promise<void> Expand (loading lazy reports first) or collapse a node. Emits aui-expand-change.
expandAll(): void; collapseAll(): void Expand every node with loaded reports (lazy nodes are not loaded) / collapse all.
search(query: string): string[] Ids whose label, title, or description contains the text (case-insensitive, locale-aware), in tree order, including collapsed nodes.
pathTo(id: string): string[] Ids from the top-level node down to the node.
reveal(id: string, options?: { focus?: boolean; select?: boolean; highlight?: boolean }): void Expand the node’s managers, center it, highlight its path (cards and connectors; default on), focus it (default) and optionally select it; announces ‘Path: A, B, C.’
clearHighlight(): void Remove the path highlight.
select(id: string | null): void Select a node without emitting aui-node-select.
centerOn(id: string): void Center the view on a visible node (keeps the zoom).
fit(): void Scale (at most 100 %) and center the whole visible chart in the viewport; announces the zoom.
zoomTo(scale: number): void Zoom around the viewport center, clamped to minZoom..maxZoom; announces ‘Zoom {percent}’ and emits aui-zoom.
moveNode(id: string, parentId: string | null, index?: number): boolean Re-parent a node (index among the new siblings, default last; null parent = top level). Returns false (and announces) when refused: cycle, locked node, or canMove. Undoable; emits aui-node-move.
undo(): boolean; redo(): boolean Undo / redo the last move (parent and position). Announce and emit aui-node-move (reason ‘undo’ / ‘redo’); false when there is nothing to undo/redo.
destroy(): void Abort loads; remove the toolbar, viewport, listeners, and host classes.
Event Detail Bubbles Cancelable Description
aui-node-select OrgChartNodeSelectDetail { id: string; node: OrgChartNode } Yes No A node was selected by click, Enter, or Space.
aui-expand-change OrgChartExpandChangeDetail { id: string; expanded: boolean } Yes No A node was expanded (after its reports loaded) or collapsed through the UI or setExpanded.
aui-node-move OrgChartNodeMoveDetail { reason: 'move' | 'undo' | 'redo'; id: string; parentId: string | null; previousParentId: string | null; index: number; nodes: OrgChartNode[] } Yes No A node got a new manager (drag, paste, toolbar, moveNode, undo, redo). Save detail.nodes or send the change to the server.
aui-zoom OrgChartZoomDetail { scale: number } Yes No The zoom changed through the toolbar, keys, Ctrl/Cmd + wheel, or zoomTo (fit does not emit).
aui-load-error OrgChartLoadErrorDetail { id: string; error: unknown } Yes No loadChildren rejected, timed out, or returned invalid nodes.
Area Description
renderNode Card content of each node (default: initials, label, title, description). Inserted once per node and re-rendered when the node’s label, title, or description changes, or renderNode changes.

All rules live in the aui.components cascade layer, so application styles override them without specificity battles.

Class Purpose
aui-org-chart Host; aui-org-chart–editing in edit mode, aui-org-chart–horizontal for the horizontal orientation.
aui-org-chart__toolbar | __tool Toolbar group and its buttons (aria-disabled at limits; the Move button is a toggle, aria-pressed).
aui-org-chart__viewport | --panning Clipping viewport (block-size 30rem by default: set block-size to size the chart); background drags pan.
aui-org-chart__canvas Transformed canvas (translate and scale through the CSSOM); –aui-org-chart-node-width and –aui-org-chart-node-height.
aui-org-chart__links | __link | __link--path Connector SVG (aria-hidden) and paths; highlighted path.
aui-org-chart__tree The role=tree element.
aui-org-chart__node | --path | --cut | --locked | --dragging | --drop | --no-drop Treeitems positioned through the CSSOM (left, top); states for the highlighted path, the node being moved, locked, dragged, and valid/invalid drop targets.
aui-org-chart__card Card box inside each node (selected: color-selected with a strong border).
aui-org-chart__avatar | __text | __name | __title | __description | __sep Default card content; __sep holds visually hidden separators for the accessible name.
aui-org-chart__toggle Expand chip with the report count (pointer target, aria-hidden; the treeitem handles keys).
Slot Default icon
toolbar zoom-out, zoom-in, maximize (fit), grip (move), check (move here), undo, redo
expand chip chevron-down / chevron-up (vertical); chevron-right / chevron-left (horizontal, mirrored in RTL)

Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.

--aui-border-width | --aui-border-width-strong | --aui-color-border | --aui-color-border-strong | --aui-color-error-fg | --aui-color-focus-ring | --aui-color-hover | --aui-color-on-primary | --aui-color-primary | --aui-color-selected | --aui-color-selected-text | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-disabled | --aui-color-text-muted | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-sm | --aui-font-size-xs | --aui-font-weight-semibold | --aui-icon-size-sm | --aui-line-height-tight | --aui-radius-md | --aui-radius-pill | --aui-radius-sm | --aui-shadow-lg | --aui-shadow-sm | --aui-space-1 | --aui-space-2 | --aui-touch-target-min

interface OrgChartNode { id: string; parentId: string | null; label: string; title?: string; description?: string; hasChildren?: boolean; locked?: boolean }
interface OrgChartRenderContext { depth: number; expanded: boolean; childCount: number; doc: Document }
type OrgChartOrientation = 'vertical' | 'horizontal';
interface OrgChartNodeSelectDetail { id: string; node: OrgChartNode }
interface OrgChartExpandChangeDetail { id: string; expanded: boolean }
interface OrgChartNodeMoveDetail { reason: 'move' | 'undo' | 'redo'; id: string; parentId: string | null; previousParentId: string | null; index: number; nodes: OrgChartNode[] }
interface OrgChartZoomDetail { scale: number }
interface OrgChartLoadErrorDetail { id: string; error: unknown }
interface OrgChartLayoutInput { id: string; children: readonly OrgChartLayoutInput[] }
interface OrgChartBox { id: string; x: number; y: number; width: number; height: number; depth: number }
interface OrgChartLayout { boxes: ReadonlyMap<string, OrgChartBox>; edges: readonly { from: string; to: string; path: string }[]; width: number; height: number }

Pattern: WAI-ARIA APG Tree View (single select) with flat treeitems carrying aria-level, aria-setsize, and aria-posinset, kept in DOM reading order, with one roving tab stop. Connectors are decorative SVG. Moves in edit mode use cut/paste keys and toolbar buttons (no deprecated ARIA drag-and-drop attributes) with live-region announcements.

Key Context Action
Tab / Shift+Tab Chart Moves through the toolbar buttons, then to the tree (one tab stop: the focused node).
ArrowDown / ArrowUp Node focused Next / previous visible node in tree order (reading order); the view pans to keep it visible.
ArrowRight (ArrowLeft in RTL) Node focused Collapsed: expands (loading reports first); expanded: moves to the first report.
ArrowLeft (ArrowRight in RTL) Node focused Expanded: collapses; otherwise moves to the manager.
Home / End Node focused First / last visible node.
Enter / Space Node focused Selects the node (aria-selected) and emits aui-node-select.
* Node focused Expands all siblings of the node.
Printable characters Node focused Typeahead: moves to the next visible node whose name starts with the typed text (0 is reserved for fit).
+ or = / - Node focused Zoom in / out around the viewport center; announces ‘Zoom {percent}’.
0 Node focused Fit the chart into the view.
Ctrl/Cmd+X Edit mode, node focused Marks the node for moving; announces ‘{label} ready to move. Go to the new manager and press Control V, or Escape to cancel.’ Locked nodes announce ‘{label} cannot be moved.’
Ctrl/Cmd+V Edit mode, a node is marked Makes the marked node a report of the focused node (last position); announces ‘{label} now reports to {parent}.’ or why it is not allowed; focus follows the moved node.
Escape Edit mode, a node is marked Cancels the move; announces ‘Move cancelled.’
Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z or Ctrl/Cmd+Y Edit mode, node focused Undo / redo the last move.
Element Role
toolbar <div> group, named ‘{label} controls’
toolbar <button>s button (native); Move is a toggle (aria-pressed)
tree <div> tree, named by aria-label
node <div> treeitem (name from its card text)
connector <svg>, expand chip, avatar none (aria-hidden=true)
Attribute Element Value
aria-label tree / toolbar label / localized ‘{label} controls’
aria-level / aria-setsize / aria-posinset treeitem Depth (1-based), number of siblings, position among siblings
aria-expanded treeitem with reports true / false (absent without reports)
aria-selected treeitem true for the selected node, false otherwise
aria-busy treeitem true while its reports load
tabindex treeitem 0 on the focused (roving) node, -1 on the others
aria-disabled toolbar buttons true at the zoom limits, and for Move / Move here / Undo / Redo when not possible
aria-pressed Move button true while a node is cut for moving
live region shared live regions Polite: zoom level, path, moved, undone, redone, move cancelled. Assertive: ready to move, not allowed, locked, load failed.

One node is in the Tab order (roving tabindex): the last focused node, else the first top-level node. Collapsing a manager of the focused node moves focus to that manager. Re-rendering (expand, move, update) keeps focus on the same node; after a paste focus moves to the moved node. reveal() focuses the node by default. The focused card shows a focus-ring outline; focusing a node pans the view so it is fully visible.

Criterion Level Note
1.3.1 Info and Relationships A
1.3.2 Meaningful Sequence A
1.4.1 Use of Color A
1.4.10 Reflow AA
1.4.11 Non-text Contrast AA
2.1.1 Keyboard A
2.4.3 Focus Order A
2.4.7 Focus Visible AA
2.5.7 Dragging Movements AA
2.5.8 Target Size (Minimum) AA
4.1.2 Name, Role, Value A
4.1.3 Status Messages AA

Automated test results: Accessibility and compliance.

  • Moving people never requires dragging (2.5.7): Ctrl/Cmd+X and Ctrl/Cmd+V from the keyboard, or select a node, press Move, select the new manager, press Move here with a single pointer.
  • Tree order in the DOM matches the visual order (managers before their reports, siblings in order), so reading order equals focus order (1.3.2, 2.4.3).
  • Selected and highlighted nodes differ by border weight and fill, not by color alone (1.4.1).
  • The report count chip is decorative; aria-expanded conveys expandability.
  • Zoom and pan are available from the keyboard and toolbar; focus movement pans automatically, so keyboard users never need to pan.
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.
  • Screen readers in browse mode may intercept arrow keys and letters; users need focus mode inside the tree.
  • Ctrl/Cmd+X, V, Z, and Y are taken in edit mode while a node has focus (no text editing happens inside the tree).
  • Pointer drags (pan, move) are not announced.

Collapsed or expanded nodes, loading reports (aria-busy), selected, focused, highlighted path, zoomed or panned, edit mode, node marked for moving, dragging over a valid or invalid target, locked.

From Trigger To
collapsed ArrowRight, chip click, setExpanded(id, true) loading (lazy, first time), then expanded
loading loadChildren rejects or times out collapsed; announced; aui-load-error
expanded ArrowLeft, chip click, setExpanded(id, false) collapsed; focus moves up if it was inside
any node click, Enter, Space selected (aui-node-select)
edit mode Ctrl/Cmd+X or Move node marked (dashed card; Move pressed)
marked Ctrl/Cmd+V or Move here on another node moved (aui-node-move), or refused and announced
marked Escape or Move again cancelled
moved Ctrl/Cmd+Z / Undo previous manager and position (reason: 'undo')
  • The accessible name of a node is its card text; the default card inserts visually hidden commas (“Ada Lovelace, CEO”).
  • Moving people never requires dragging (WCAG 2.5.7): Ctrl/Cmd+X and Ctrl/Cmd+V, or with a single pointer select a node, press Move, select the new manager, press Move here.
  • Focus movement pans automatically, so keyboard users never need to pan; zoom is available from keys and the toolbar.
  • Selection and the highlighted path differ by border weight and fill, not color alone.
  • Manual screen-reader verification (NVDA, JAWS, VoiceOver) is pending.

The viewport has a fixed block size (30rem; set it with CSS) and clips the chart; the chart pans and zooms instead of scrolling, so the page reflows at 320 px. fit() scales the visible chart into any viewport (at most 100 %). Touch drags on the background pan. Toolbar buttons are touch-target-min squares.

direction: 'rtl' mirrors the layout: the first report is on the right in vertical charts, and horizontal charts grow from right to left. ArrowLeft and ArrowRight swap roles; expand chips point the mirrored way.

Cards use color-surface, color-border-strong, radius-md, and shadow-sm; the selected card uses color-selected with color-selected-text and a strong border. The highlighted path uses color-focus-ring on card borders and thicker connectors. Forced colors: CanvasText connectors, Highlight for the path and selection. Position transitions are removed under reduced motion.

configureAbsoluteUI({ components: { orgChart: { orientation: 'horizontal', nodeWidth: 180, nodeHeight: 56, expandDepth: 1, maxZoom: 3 } } });

Global options: orientation, nodeWidth, nodeHeight, siblingGap, levelGap, expandDepth, minZoom, maxZoom, requestTimeoutMs, plus inherited locale and direction. Changes apply to existing charts immediately (the expansion is kept).

Instance options override global and theme defaults. All options are dynamic through update(); nodes replaces the data and clears the undo history; expanded or expandDepth resets the expansion.

Toolbar: zoom-out, zoom-in, maximize (fit), grip (Move), check (Move here), undo, redo. Expand chip: chevron-down / chevron-up (vertical), chevron-right / chevron-left (horizontal, mirrored in RTL).

Names, titles, and descriptions render through textContent; custom content comes from renderNode as DOM nodes owned by the application. Nodes and loaded reports are validated (unique ids, known parents, no cycles) before use. Positions and transforms are written through the CSSOM only (CSP-safe). loadChildren receives an AbortSignal and times out.

No persistence, network access, or telemetry of its own. Edits are reported through aui-node-move (detail.nodes) and the nodes getter; the application saves them. Export creates files locally through temporary object URLs.

The layout is a contour-based tidy tree (per-depth extents, linear in nodes times depth). Nodes are rendered once and reused by id; only visible (expanded) nodes exist in the DOM. Pan and zoom change one CSS transform. Budgets (e2e): create with 1,000 visible nodes < 80 ms; expand or collapse with 1,000 visible nodes < 80 ms; layoutOrgChart for 5,000 nodes < 60 ms.

Tested with 1,000 visible nodes (5,000 for the layout) and any number of collapsed nodes. There is no virtualization: collapse levels (expandDepth) and load teams lazily for larger organizations.

  • Several top-level nodes (parentId: null) are laid out side by side.
  • A node with hasChildren but no loaded reports is expandable; after a load with no reports it becomes a leaf.
  • Collapsing a manager moves focus to the manager when focus was inside the collapsed team.
  • Moving a node under one of its own reports, or moving a locked node, is refused and announced; canMove can refuse more.
  • Moves append the node last among its new siblings; undo restores the exact position.
  • update({ nodes }) keeps the expansion of ids that still exist and clears the undo history.
  • Long names are truncated visually; the full text stays in the accessible name.
Code Category Cause
ORG_CHART_OPTIONS configuration Empty label; invalid nodes (duplicate ids, unknown parents, cycles, empty labels, wrong types); invalid expanded, selected, callbacks, historyLimit; reveal, select, or moveNode with unknown ids; zoomTo with a non-positive scale; invalid nodes from loadChildren (reported through aui-load-error).
ORG_CHART_EXPORT_OPTIONS configuration Invalid export options (see org-chart-export.md).

A failed construction leaves the host untouched.

  • No virtualization; keep visible nodes to a few thousand.
  • Fixed card size; long text is truncated.
  • No keyboard command for reordering siblings (use moveNode with an index).
  • A plain wheel scrolls the page; no pinch zoom.
  • Pointer drags are not announced.
  • Export redraws label and title only.
  • Manual screen-reader verification is pending.
  • Several managers per person, or graphs with cycles.
  • Assistant positions beside a manager; compact stacking of leaf reports.
  • Adding, deleting, or renaming people inside the chart (update nodes).
  • Multi-page printing.