Organization Chart
import { OrgChart, type OrgChartNode } from '@absoluteui/components/org-chart';import '@absoluteui/components/org-chart/style.css';Using Angular? See Organization Chart for Angular.
What it does
Section titled “What it does”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.
When to use
Section titled “When to use”- 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).
When not to use
Section titled “When not to use”- 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.
Examples
Section titled “Examples”A company chart from flat nodes
Section titled “A company chart from flat nodes”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();Load teams on demand and find a person
Section titled “Load teams on demand and find a person”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);Custom card content and export
Section titled “Custom card content and export”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);Pure layout (no DOM)
Section titled “Pure layout (no DOM)”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 });}More usage
Section titled “More usage”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.
Anatomy
Section titled “Anatomy”<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)API reference
Section titled “API reference”Factory
Section titled “Factory”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).
Options
Section titled “Options”| 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. |
Properties
Section titled “Properties”| 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). |
Methods
Section titled “Methods”| 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. |
Events
Section titled “Events”| 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. |
Content areas
Section titled “Content areas”| 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. |
CSS classes
Section titled “CSS classes”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). |
Icon slots
Section titled “Icon slots”| 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) |
Design tokens
Section titled “Design tokens”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
TypeScript definitions
Section titled “TypeScript definitions”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 }Accessibility
Section titled “Accessibility”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.
Keyboard
Section titled “Keyboard”| 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) |
ARIA attributes
Section titled “ARIA attributes”| 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.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- 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.
Behavior
Section titled “Behavior”States
Section titled “States”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.
State transitions
Section titled “State transitions”| 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') |
Accessibility notes
Section titled “Accessibility notes”- 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.
Responsive behavior
Section titled “Responsive behavior”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.
Right-to-left
Section titled “Right-to-left”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.
Theming
Section titled “Theming”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.
Global configuration
Section titled “Global configuration”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).
Component configuration
Section titled “Component configuration”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).
Security
Section titled “Security”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.
Data handling
Section titled “Data handling”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.
Performance
Section titled “Performance”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.
Supported data scale
Section titled “Supported data scale”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.
Edge cases
Section titled “Edge cases”- Several top-level nodes (
parentId: null) are laid out side by side. - A node with
hasChildrenbut 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;
canMovecan 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.
Errors
Section titled “Errors”| 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.
Known limitations
Section titled “Known limitations”- No virtualization; keep visible nodes to a few thousand.
- Fixed card size; long text is truncated.
- No keyboard command for reordering siblings (use
moveNodewith 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.
Unsupported scenarios
Section titled “Unsupported scenarios”- 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.