Skip to content
ShineUI
Install

OrgChart

Who reports to whom.

<sui-org-chart>DataStable

About

Reporting-line chart for who reports to whom. The connectors are CSS, not canvas. The lead node renders on the brand chrome with an accent avatar; everyone else is a surface card. Branches collapse and cards take a selection on request. Pass root, a nested { label, sub, initials, lead, children } object.

Usage

<sui-org-chart></sui-org-chart> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-org-chart"); el.root = { label: "Dana Whitfield", sub: "Admin, Controller", lead: true, children: [{ label: "Marcus Lee", sub: "Accountant" }, { label: "Priya Raman", sub: "A/P clerk" }], }; /* SuiOrgNode | undefined */ el.addEventListener("suiSelectionChange", (e) => console.log(e.detail)); </script>
import { SuiOrgChart } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { const root = { label: "Dana Whitfield", sub: "Admin, Controller", lead: true, children: [{ label: "Marcus Lee", sub: "Accountant" }, { label: "Priya Raman", sub: "A/P clerk" }], }; /* SuiOrgNode | undefined */ return <SuiOrgChart root={root} onSuiSelectionChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiOrgChart } from "@shineui/vue"; import "@shineui/core/shineui.css"; const root = { label: "Dana Whitfield", sub: "Admin, Controller", lead: true, children: [{ label: "Marcus Lee", sub: "Accountant" }, { label: "Priya Raman", sub: "A/P clerk" }], }; // SuiOrgNode | undefined const onChange = (e) => console.log(e.detail); </script> <template> <SuiOrgChart :root="root" @sui-selection-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiOrgChart } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiOrgChart], template: `<sui-org-chart [(selection)]="selection" [root]="root"></sui-org-chart>`, }) export class Example { selection: SuiKey[] = []; root = { label: "Dana Whitfield", sub: "Admin, Controller", lead: true, children: [{ label: "Marcus Lee", sub: "Accountant" }, { label: "Priya Raman", sub: "A/P clerk" }], }; // SuiOrgNode | undefined }

Basic

root is a property, a nested { label, sub, lead, children } object. The chart is a role="tree" of treeitem nodes with aria-level, each labeled with the name and role line together; the cards take no focus until collapsible or a selection mode is set.

<sui-org-chart></sui-org-chart>
// setup(root): properties and events setup(root) { root.querySelector('sui-org-chart').root = controller([marcus, priya]); }

Three levels and initials

Initials come from the label unless a node carries its own, which is how a name the derivation reads wrong ("A/P Team") gets the right avatar. The avatar is aria-hidden, so the initials are not read twice.

<sui-org-chart id="oc-deep"></sui-org-chart>
// setup(root): properties and events setup(root) { root.querySelector('#oc-deep').root = controller([ { ...marcus, children: [{ label: 'Robin Vasquez', sub: 'Bookkeeper' }] }, { label: 'A/P Team', sub: 'Payables', initials: 'AP', children: [priya, { label: 'Cy Booth', sub: 'A/P clerk' }], }, ]); }

No lead node

Drop lead and the root renders as a white card like the rest. A row wider than the page scrolls sideways inside the chart, so a broad team does not stretch the layout around it.

<div style="max-width:520px"><sui-org-chart id="oc-wide"></sui-org-chart></div>
// setup(root): properties and events setup(root) { root.querySelector('#oc-wide').root = { label: 'Month end close', sub: 'Period 8', children: [ { label: 'Bank reconciliation', sub: 'Marcus Lee' }, { label: 'Payables cutoff', sub: 'Priya Raman' }, { label: 'Accruals', sub: 'Robin Vasquez' }, { label: 'Fixed assets', sub: 'Cy Booth' }, ], }; }

Collapsible branches

collapsible draws a chevron under every card that has reports, and suiExpandedChange reports the keys of the open branches, the same set sui-tree reports. Leave expanded unset and every branch is open; write it to open a chosen few. A card with no key of its own is addressed by its path, "r.0".

expanded: all
<div style="display:grid;gap:10px"> <code id="oc-shut-log" style="font:460 12px/1.6 var(--font-sans);color:var(--ink-muted)">expanded: all</code> <sui-org-chart id="oc-shut" collapsible></sui-org-chart> </div>
// setup(root): properties and events setup(root) { const el = root.querySelector('#oc-shut'); const log = root.querySelector('#oc-shut-log'); el.root = team(); el.addEventListener('suiExpandedChange', (e) => { log.textContent = `expanded: ${e.detail.length ? e.detail.join(', ') : 'none'}`; }); }

Empty and loading

With no root the chart shows empty-message, "Nothing here yet." unless set. loading puts one status row in its place and marks the host aria-busy while the tree is on its way.

<div style="display:grid;gap:14px"> <sui-org-chart empty-message="No team has been set up for this office."></sui-org-chart> <sui-org-chart loading></sui-org-chart> </div>

Selection

selection-mode="single" marks one card with aria-selected; "checkbox" puts a box on every card with aria-checked, one card at a time, no cascade to the reports below. Both write selection before suiSelectionChange fires. Neither it nor suiExpandedChange bubbles: listen on the chart.

selection: none
<div style="display:grid;gap:14px"> <code id="oc-pick-log" style="font:460 12px/1.6 var(--font-sans);color:var(--ink-muted)">selection: none</code> <sui-org-chart id="oc-single" selection-mode="single"></sui-org-chart> <sui-org-chart id="oc-many" selection-mode="checkbox"></sui-org-chart> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#oc-pick-log'); root.querySelectorAll('#oc-single, #oc-many').forEach((el) => { el.root = team(); el.addEventListener('suiSelectionChange', (e) => { log.textContent = `selection: ${e.detail.length ? e.detail.join(', ') : 'none'}`; }); }); }

Keyboard

KeyAction
TabMoves into the chart, to the card the arrows last left, and out of it on the next press.
ArrowLeft ArrowRightMoves to the previous or next card in the same row of reports. Under dir="rtl" the pair swaps, so the arrow that points along the reading direction is always the forward one.
ArrowDownOpens a shut branch, or moves to the first report under the card.
ArrowUpShuts an open branch, or moves to the card this one reports to.
Home EndMoves to the root card, or to the last card in reading order.
EnterPicks the card. With no selection mode it opens or shuts the branch instead.
SpacePicks the card.

API

<sui-org-chart>

Properties

PropertyAttributeTypeDefaultDescription
collapsiblecollapsiblebooleanfalseDraws a chevron under every card that has children, and lets the keyboard shut a branch. Reflected.
emptyMessageempty-messagestringstring | undefinedSentence shown when there is no root. Defaults to "Nothing here yet."; the chart filters nothing, so this is its only empty string.
expandedproperty onlySuiKey[]SuiKey[] | undefinedKeys of the branches whose reports are shown. A node's key is its own key, or its path from the root ("r.0.1"). Leave it unset and every branch is open, which is how a chart rests; set it to [] to shut them all. Mutable; updated before suiExpandedChange fires. Property, not attribute.
loadingloadingbooleanfalseThe tree is still on its way: one status row stands in for the chart and the host is aria-busy. Reflected, so a page can style the wait.
rootproperty onlySuiOrgNodeSuiOrgNode | undefinedRoot node of the tree: { label, key?, sub?, initials?, lead?, children? }. Exposed as tree > treeitem > group with aria-level, so the hierarchy is announced. Property, not attribute.
selectionproperty onlySuiKey[][]Keys of the selected cards, as every collection holds its selection: one key in single mode, any number in checkbox mode. Mutable; updated before suiSelectionChange fires. Property, not attribute.
selectionModeselection-modeSuiOrgChartSelectionMode"checkbox" | "none" | "single"'none'none (display only, no focus), single (one picked card, aria-selected) or checkbox (many, aria-checked, a box on each card). Checkbox mode picks one card at a time and does not cascade to the reports below it, which is what sui-tree does with the same word. Reflected.

Events

EventDetailReactVueDescription
suiExpandedChangeSuiKey[]onSuiExpandedChange@sui-expanded-changeFires when a branch opens or closes; detail is the array of expanded keys. Does not bubble.
suiSelectionChangeSuiKey[]onSuiSelectionChange@sui-selection-changeFires when the selection changes; detail is the new selection. Does not bubble.

Methods

MethodSignatureDescription
setFocus()setFocus() => Promise<void>Moves keyboard focus to the chart's single tab stop: the active card, else the root, on a chart that collapses or selects; the chart itself on a picture wider than its box, which is its tab stop for scrolling. Does nothing on a picture that fits.

Types

SuiKey typeA record's or an item's key wherever the library identifies one: a row, a tree node, a list item, a selected value. Numbers stay numbers, so a numeric id round-trips through selection, the events and a framework binding without turning into a string.
No members.
SuiOrgChartSelectionMode typeHow many cards a caller can pick: not at all, one at a time, or many through a box on the card.
No members.
SuiOrgNode interfaceOne person or role in the reporting tree.
labelstring
key?SuiKeyIdentity for expanded and selection, kept as given: a numeric id stays a number. Defaults to the node's path from the root ("r.0.1").
sub?stringRole line under the name ("Controller").
initials?stringAvatar initials; derived from the label when omitted.
lead?booleanLead node: a brand card with an accent avatar.
children?SuiOrgNode[]

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also