Org Chart
Render a people-based organization chart from flat reporting relationships.
OrgChart renders a rooted reporting hierarchy from app-owned people and flat manager-to-report facts.
It uses the same tree core as FamilyTree: measured cards, SVG edges, pan/scroll viewport state, accessible card props, custom React cards, and the tiny stylesheet.
Simple Mode
import { OrgChart, org } from "@memoir/tree";
import "@memoir/tree/styles.css";
const people = {
ceo: { id: "ceo", name: "Casey", title: "CEO" },
engineering: { id: "engineering", name: "Morgan", title: "VP Engineering" },
design: { id: "design", name: "Riley", title: "Design Lead" },
};
const relationships = [
org.manager("ceo", ["engineering", "design"]),
];
export function TeamChart() {
return <OrgChart people={people} root="ceo" relationships={relationships} />;
}You can omit card to use DefaultOrgCard.
Graph Mode
Graph mode gives each manager-report edge an app-owned identity and order. Use it when reporting facts come from persisted records rather than a hand-authored helper list.
import { OrgChart, type OrgChartGraph } from "@memoir/tree";
const graph: OrgChartGraph<Person> = {
people,
root: "ceo",
reportingLinks: [
{ id: "ceo-engineering", managerId: "ceo", reportId: "engineering", relation: "manager", status: "current", order: 1 },
{ id: "ceo-design", managerId: "ceo", reportId: "design", relation: "direct", status: "former", order: 2 },
],
};
export function TeamChart() {
return <OrgChart graph={graph} />;
}Custom Card
import type { OrgCardProps } from "@memoir/tree";
type Person = (typeof people)[string];
function TeamCard({ depth, person, ...rootProps }: OrgCardProps<Person>) {
return (
<article {...rootProps}>
<strong>{person.name}</strong>
<small>{person.title ?? `level ${depth}`}</small>
</article>
);
}
<OrgChart
people={people}
root="ceo"
relationships={relationships}
card={TeamCard}
/>The card receives accessibility props, keyboard handlers, stable data-* attributes, depth metadata, selection state, and the original person record. Spread the remaining props onto the card root.
renderCard is also available when you prefer a render function. Spread rootProps onto the root element:
<OrgChart
people={people}
root="ceo"
relationships={relationships}
renderCard={({ person, rootProps }) => (
<article {...rootProps}>
<strong>{person.name}</strong>
<small>{person.title}</small>
</article>
)}
/>Relationship Helper
org.manager("ceo", ["vp-eng", "vp-sales"], {
relation: "manager",
status: "current",
order: 0,
});
org.report("vp-eng", "staff-engineer");
org.reports("ceo", ["vp-eng", "vp-sales"]);OrgChart models people and reporting lines. Titles, teams, permissions, and actions belong on your person records or custom card props.
Common Options
<OrgChart
people={people}
root="ceo"
relationships={relationships}
selected={selectedPersonId}
collapsed={["engineering"]}
maxDepth={3}
onPersonClick={(_person, personId) => setSelectedPersonId(personId)}
/>Use selected for app-owned selection, collapsed to hide deeper reports under a person, and maxDepth to cap the rendered hierarchy.
Default spacing is compact:
{ row: 80, column: 24, padding: 24 }Override spacing when your custom org cards need more or less room. The default interactionMode is "pan"; users can drag the canvas and non-interactive card surfaces with mouse, touch, or pen. Use "pan-page-scroll" when mouse or horizontal touch should drag the tree and vertical touch should scroll the page. Native controls inside custom cards keep their own pointer behavior.
The root is centered by default after cards are measured. Use defaultViewport for a custom uncontrolled starting position or initialViewport for explicit modes like "canvas". treeApiRef exposes centerPerson(personId), fitToSubject(), and resetViewport().