API
Public exports from @memoir/tree.
All public package exports come from @memoir/tree. Import the stylesheet separately from @memoir/tree/styles.css.
import {
FamilyTree,
OrgChart,
buildFamilyTreeLayout,
buildOrgChartLayout,
buildLayeredTreeLayout,
collectFamilyNeighborhood,
collectOrgChartSubtree,
createFamilyIndex,
createFamilyLayoutService,
createOrgChartIndex,
createUnionParentLinks,
defaultFamilyLayoutOptions,
defaultFamilyNeighborhoodLimits,
getFamilyChildBearingGroupIds,
getFamilyChildPlacementGroupIds,
getFamilyPartnershipGroupIds,
getTreeStyleName,
graphToFamilyRelationships,
graphToOrgReportingRelationships,
layoutFamilyTree,
org,
rel,
resolveFamilyLayoutOptions,
treeStylePresets,
} from "@memoir/tree";
import type {
FamilyCardProps,
FamilyGraph,
OrgCardProps,
OrgChartGraph,
TreeApi,
TreeLayeredBoxInput,
TreeViewport,
} from "@memoir/tree";
import "@memoir/tree/styles.css";Components
FamilyTree: subject-centered family neighborhood renderer.OrgChart: rooted manager/report hierarchy renderer.DefaultFamilyCard: small built-in family card.StyledFamilyCard: configurable family card for quick demos and simple apps.DefaultOrgCard: small built-in org card.
Styling Exports
treeStylePresets: readonly list of built-in theme names, currentlymemoirandsystem.getTreeStyleName(theme): resolves an optional theme to a concrete preset name, defaulting tomemoir.
Most apps only need the theme prop and CSS variables. These helpers are useful for wrappers that need to mirror the package's preset names.
Layout Exports
buildFamilyTreeLayout(input): React-free family layout used byFamilyTree. It accepts graph or simple relationship data and returns positioned cards, SVG edge paths, and bounds.createFamilyLayoutService(options?): lower-level family service for app code that needs old-stylepeople,unions,parentLinks, andcentercontrol while still using the current graph/layout model.createUnionParentLinks(union, child, options?): helper for add-child flows where the selected UI context is a union.getFamilyPartnershipGroupIds(graph, personId): returns stable partnership group IDs involving a person, sorted by group order.getFamilyChildBearingGroupIds(graph, personId): returns stable group IDs where a person already owns parent or guardian child links.getFamilyChildPlacementGroupIds(graph, personId): returns stable group IDs where a person can place a child, including existing partnership groups before they have child links.layoutFamilyTree(input): convenience wrapper aroundcreateFamilyLayoutService().layout(input).buildLayeredTreeLayout(input): small measured-box layout primitive used by the renderers.
Use FamilyTree for the normal app UI. Use buildFamilyTreeLayout() when you need to render the same family layout yourself or build a custom non-React surface. React card size stays owned by rendered components and CSS; the React wrapper only passes measured card dimensions into this layout function.
Use createFamilyLayoutService() when you need lower-level data similar to the earlier layout API. It returns person nodes, union nodes, edges, bounds, warnings, and the derived FamilyGraph. Union entities can carry app-owned data.
When an app adds a child from a selected union context, use createUnionParentLinks() so the child is owned by that union and the edge routes from the union. Parent-only links without a union ID intentionally create a single-parent/synthetic union instead of guessing among a person's possible unions.
For selected-card add flows, keep relation permissions and dialogs in your app. Use getFamilyPartnershipGroupIds(), getFamilyChildBearingGroupIds(), or getFamilyChildPlacementGroupIds() to find the relevant union IDs, then persist the chosen groupId on the new parent-child links.
Family Props
Recommended graph-mode props:
graph: graph-shaped family facts withpeople,subject,partnershipGroups,parentChildLinks, and optionalguardianshipLinks.
When graph is provided, FamilyTree uses the graph's people, subject, and relationships derived from graph links.
The graph is normal app state. Add or remove family members by updating that state and re-rendering:
setGraph((current) => ({
...current,
people: {
...current.people,
[childId]: { id: childId, name: "New child" },
},
parentChildLinks: [
...current.parentChildLinks,
{ id: `alex-${childId}`, groupId: "alex-jordan", parentId: "alex", childId },
{ id: `jordan-${childId}`, groupId: "alex-jordan", parentId: "jordan", childId },
],
}));The shared groupId is what attaches the child to the union instead of one arbitrary parent card. Keep IDs stable after creation for clean JSON diffs.
Simple-mode alternative:
people: person records keyed by ID.subject: person ID at the center of the family neighborhood.relationships: flat parentage, partnership, and guardianship facts from helpers likerel.parents().
Graph shape:
type PersonId = string;
interface FamilyGraph<Person = unknown> {
people: Record<PersonId, Person>;
subject: PersonId;
partnershipGroups: FamilyPartnershipGroup[];
parentChildLinks: FamilyParentChildLink[];
guardianshipLinks?: FamilyGuardianshipLink[];
}
interface FamilyPartnershipGroup {
id: string;
partners: PersonId[];
relation?: "spouse" | "partner" | "coparent" | "unknown";
status?: "current" | "former" | "divorced" | "separated" | "unknown";
order?: number;
data?: unknown;
}
interface FamilyParentChildLink {
id?: string;
groupId?: string;
parentId: PersonId;
childId: PersonId;
relation?: "biological" | "adoptive" | "step" | "foster" | "unknown";
status?: "current" | "former" | "divorced" | "separated" | "unknown";
order?: number;
}
interface FamilyGuardianshipLink {
id?: string;
groupId?: string;
guardianId: PersonId;
childId: PersonId;
relation?: "guardian" | "foster" | "unknown";
status?: "current" | "former" | "unknown";
order?: number;
}Graph normalization groups parentChildLinks by groupId, relation, status, and order. Links that share those values become one rendered parentage relationship with a deterministic rendered ID. Links with different relation kinds, such as biological plus step within one partnership group, remain separate so each edge can keep its own kind. Parentage does not imply a spouse, partner, or co-parent relationship; visible relationship bars come from partnershipGroups.
In graph mode, grouped child edges expose the relationship groupId as their source ID. That keeps the rendered edge associated with the parent group in your app state instead of arbitrarily assigning it to one parent.
Common optional props:
card: custom React card component.cardProps: extra typed props for every custom card, or a function per person.selected: app-owned selected person ID.collapsed: person IDs whose deeper ancestor or descendant rows should be hidden.limits: visible-relative caps and generation depth; usenullto disable a cap.onPersonClick: card click and Enter/Space activation handler.onAddRelationship: passed to cards for app-owned add-relative flows.getPersonLabel: accessible card label builder.viewport,initialViewport,defaultViewport,onViewportChange,treeApiRef: viewport control.interactionMode,spacing,lineShape,theme,className,style,cardClassName,edgeClassName: rendering and styling controls.
Family layout is subject-centered and renders a bounded neighborhood: ancestor generations, the subject row with siblings and partners, and descendant generations. Lateral branches such as parent siblings, cousins, and nieces/nephews render only when lateralFamilyGenerations is above 0. Partnership groups and child groups are layout inputs, so children from separate unions anchor to the correct visible parent pair. A person renders at most once; direct roles such as parent, guardian, child, partner, and co-parent outrank lower-priority sibling and lateral paths.
Two-parent child groups join from the parent cards into a child bus. Multi-child groups split through a horizontal bus centered in the clear vertical gap between the parent cards and child row. The routing does not synthesize a relationship bar between two parents unless an explicit partnership group is visible.
Unknown partner placeholders are display/layout facts, not real spouse bars. A partnership with relation: "unknown" or status: "unknown" renders the visible placeholder card without drawing a horizontal partnership edge. If that placeholder is also an actual co-parent, include it in the child parentChildLinks or rel.children([...parents], children) parent list so the parentage edge connects from both parents.
Default family spacing:
{ row: 80, column: 24, padding: 24 }Default family limits are:
{
ancestorGenerations: 2,
descendantGenerations: 2,
lateralFamilyGenerations: 0,
grandparents: 4,
parents: 4,
siblings: 8,
halfSiblings: 8,
auntsUncles: 8,
cousins: 12,
niecesNephews: 12,
partners: 3,
children: 8,
grandchildren: 8,
}Family cards also receive graph placement metadata when available:
placement?: {
partnershipGroupIds: string[];
parentChildLinkIds: string[];
guardianshipLinkIds: string[];
visibleRelationshipIds: string[];
};Org Props
Recommended graph-mode props:
graph: graph-shaped org facts withpeople,root, andreportingLinks.
When graph is provided, OrgChart uses the graph's people, root, and relationships derived from graph links.
Simple-mode alternative:
people: person records keyed by ID.root: root person ID for the hierarchy.relationships: flat reporting facts.
Graph shape:
interface OrgChartGraph<Person = unknown> {
people: Record<PersonId, Person>;
root: PersonId;
reportingLinks: OrgReportingLink[];
}
interface OrgReportingLink {
id?: string;
managerId: PersonId;
reportId: PersonId;
relation?: "manager" | "direct" | "unknown";
status?: "current" | "former" | "unknown";
order?: number;
}Graph normalization groups reportingLinks by managerId, relation, status, and order. Links that share those values become one reporting relationship, while each rendered edge keeps its app-owned link id.
Common optional props:
card: custom React card component.renderCard: render-function shortcut that receivesrootProps.cardProps: extra typed props for every custom card, or a function per person.selected: app-owned selected person ID.collapsed: person IDs whose reports should be hidden.maxDepth: deepest report level to render.onPersonClick: card click and Enter/Space activation handler.getPersonLabel: accessible card label builder.viewport,initialViewport,defaultViewport,onViewportChange,treeApiRef: viewport control.interactionMode,spacing,lineShape,theme,className,style,cardClassName,edgeClassName: rendering and styling controls.
Default org chart spacing:
{ row: 80, column: 24, padding: 24 }Relationship Helpers
rel.parents("alex", ["morgan", "casey"]);
rel.children(["alex", "jordan"], ["riley"]);
rel.partner("alex", "jordan", { relation: "spouse" });
rel.guardians("riley", ["morgan"]);
org.manager("ceo", ["vp-eng", "vp-sales"]);
org.report("vp-eng", "staff-engineer");
org.reports("ceo", ["vp-eng", "vp-sales"]);rel.parents() and rel.children() default to biological parentage. rel.partner() defaults to relation: "partner" and status: "current". rel.guardians() defaults to relation: "guardian". These are simple-mode helpers; prefer graph mode for production family-tree apps that need parent groups, multiple unions, per-parent lineage, or guardianship.
org.manager(), org.report(), and org.reports() create reporting relationships. They default to relation: "manager" and status: "current".
Family relations:
- Parentage:
biological,adoptive,step,foster,unknown. - Partnership:
spouse,partner,coparent,unknown. - Guardianship:
guardian,foster,unknown. - Status:
current,former,divorced,separated,unknown.
Org reporting:
- Relation:
manager,direct,unknown. - Status:
current,former,unknown.
Viewport API
Trees center the subject or org root by default after card measurement. Use defaultViewport for a custom uncontrolled starting position, or initialViewport for explicit viewport modes:
<FamilyTree defaultViewport={{ x: 120, y: 40 }} />
<FamilyTree initialViewport="canvas" />
<FamilyTree initialViewport="subject" />
<FamilyTree initialViewport={{ mode: "center-person", personId: "alex" }} />
<OrgChart initialViewport={{ mode: "center-root" }} />
<FamilyTree initialViewport={{ x: 120, y: 40 }} />Pass treeApiRef to call:
centerPerson(personId): center a rendered card.fitToSubject(): center the current family subject or org root.resetViewport(): return toinitialViewport,defaultViewport, or the default subject/root-centered position.
interactionMode controls pointer behavior:
"pan": default. Drag the canvas or non-interactive card surfaces with mouse, touch, or pen to move the viewport."pan-page-scroll": drag with mouse or horizontal touch gestures while allowing vertical touch gestures to scroll the page."scroll": use normal browser scrollbars."none": disable viewport interaction.
Buttons, links, inputs, selects, textareas, contenteditable elements, and elements marked with data-tree-drag-ignore do not start pan drags.
Pure Helpers
Use these in tests, previews, or custom renderers:
graphToFamilyRelationships(graph)graphToOrgReportingRelationships(graph)createFamilyIndex(people, relationships)collectFamilyNeighborhood(index, subject, limits?)buildFamilyTreeLayout(input)createFamilyLayoutService(options?)createUnionParentLinks(union, child, options?)getFamilyPartnershipGroupIds(graph, personId)getFamilyChildBearingGroupIds(graph, personId)getFamilyChildPlacementGroupIds(graph, personId)layoutFamilyTree(input)buildLayeredTreeLayout(input)createOrgChartIndex(people, relationships)collectOrgChartSubtree(index, root, options?)buildOrgChartLayout(input)
buildLayeredTreeLayout() is a small measured-box helper for custom renderers. It places boxes in ordered layers, supports internal anchor points, prevents row overlap, and returns positioned boxes plus bounds. It is intentionally not a graph editor or a bundled Dagre/ELK dependency.
const layers: TreeLayeredBoxInput[][] = [
[
{
id: "parents",
width: 240,
height: 80,
anchorPoints: [
{ id: "parent-a", offsetX: 60 },
{ id: "parent-b", offsetX: 180 },
],
},
],
[{ id: "child", width: 120, height: 64, anchorIds: ["parent-a", "parent-b"] }],
];
const layout = buildLayeredTreeLayout({
layers,
spacing: { row: 96, column: 32, padding: 24 },
});anchorIds target box IDs or anchorPoints from earlier layers. When multiple anchors are provided, the box targets their average center. Rows are balanced to avoid overlap while staying near their targets.
Lower-Level Primitives
TreeProvider, TreeCanvas, TreeEdges, TreeNodeLayer, and useTreeLayout() expose the family renderer layers for custom composition. They are family primitives, not generic graph primitives. Import them from @memoir/tree.
Use FamilyTree and OrgChart unless you need to own the family render layers directly.
Types
Common exports include:
PersonId,PeopleByIdFamilyTreeProps,FamilyCardProps,FamilyGraph,FamilyPartnershipGroup,FamilyParentChildLink,FamilyGuardianshipLink,FamilyRelationship,ComputedRelation,FamilyNeighborhoodLimitsFamilyLayoutInput,FamilyLayoutResult,FamilyLayoutNode,FamilyPersonLayoutNode,FamilyUnionLayoutNode,FamilyLayoutWarning,CreateUnionParentLinksOptionsOrgChartProps,OrgCardProps,OrgChartGraph,OrgReportingLink,OrgReportingRelationshipTreeApi,TreeViewport,TreeInteractionMode,TreeLineShapeTreeStylePreset,TreeThemeStyleTreeLayeredBoxInput,TreeLayeredBox,TreeLayeredAnchorPointInput