@memoir/tree
Memoir Labs

App Structure

A simple way to organize app code that uses @memoir/tree.

You do not need to copy this library's internals into your app. Install the package, keep your existing data model, and add the tree where you show family or org context.

Family Setup

app/
  family/
    page.tsx
    family-data.ts
    family-actions.ts
    profile-card.tsx

For small examples, family-data.ts can use simple relationship helpers:

import { rel } from "@memoir/tree";

export const people = {
  alex: { id: "alex", name: "Alex" },
  morgan: { id: "morgan", name: "Morgan" },
  casey: { id: "casey", name: "Casey" },
};

export const relationships = [
  rel.parents("alex", ["morgan", "casey"]),
];

For production family apps, keep graph-shaped facts in the data module instead:

import type { FamilyGraph } from "@memoir/tree";

type Person = {
  id: string;
  name: string;
};

export const graph: FamilyGraph<Person> = {
  people: {
    alex: { id: "alex", name: "Alex" },
    morgan: { id: "morgan", name: "Morgan" },
    riley: { id: "riley", name: "Riley" },
  },
  subject: "riley",
  partnershipGroups: [{ id: "alex-morgan", partners: ["alex", "morgan"], relation: "spouse" }],
  parentChildLinks: [
    { id: "alex-riley", groupId: "alex-morgan", parentId: "alex", childId: "riley" },
    { id: "morgan-riley", groupId: "alex-morgan", parentId: "morgan", childId: "riley" },
  ],
};

profile-card.tsx owns card markup:

import type { FamilyCardProps } from "@memoir/tree";

type Person = {
  id: string;
  name: string;
};

export function ProfileCard({ person, relation, ...rootProps }: FamilyCardProps<Person>) {
  return (
    <article {...rootProps}>
      <strong>{person.name}</strong>
      <small>{relation.label}</small>
    </article>
  );
}

page.tsx renders the tree:

"use client";

import { useState } from "react";
import { FamilyTree } from "@memoir/tree";
import "@memoir/tree/styles.css";
import { graph } from "./family-data";
import { addChildToGroup } from "./family-actions";
import { ProfileCard } from "./profile-card";

export default function FamilyPage() {
  const [familyGraph, setFamilyGraph] = useState(graph);

  return (
    <>
      <button
        type="button"
        onClick={() => setFamilyGraph((current) => addChildToGroup(current, "alex-morgan"))}
      >
        Add child
      </button>
      <FamilyTree graph={familyGraph} card={ProfileCard} />
    </>
  );
}

family-actions.ts can hold small graph update helpers:

import type { FamilyGraph } from "@memoir/tree";

type Person = {
  id: string;
  name: string;
};

export function addChildToGroup(graph: FamilyGraph<Person>, groupId: string): FamilyGraph<Person> {
  const group = graph.partnershipGroups.find((partnership) => partnership.id === groupId);
  if (!group) return graph;

  const childId = crypto.randomUUID();

  return {
    ...graph,
    people: {
      ...graph.people,
      [childId]: { id: childId, name: "New child" },
    },
    parentChildLinks: [
      ...graph.parentChildLinks,
      ...group.partners.map((parentId) => ({
        id: `${parentId}-${childId}`,
        groupId,
        parentId,
        childId,
        relation: "biological" as const,
      })),
    ],
  };
}

That helper returns a new graph. React re-renders FamilyTree from the new state, and your app can persist the same graph JSON.

Keep layout options close to the route or panel that owns the experience:

<FamilyTree
  graph={graph}
  card={ProfileCard}
  spacing={{ row: 80, column: 24, padding: 24 }}
/>

Use interactionMode="pan-page-scroll" when the tree should drag with mouse or horizontal touch gestures, while vertical touch gestures scroll the page. Use interactionMode="scroll" only when you want native scrollbars instead of drag panning.

Simple Org Setup

app/
  team/
    page.tsx
    org-data.ts
    org-card.tsx

Use org.manager in the data module and render OrgChart from the page. Keep titles, teams, permissions, and actions on your person records or card props.

import { org } from "@memoir/tree";

export const people = {
  ceo: { id: "ceo", name: "Casey", title: "CEO" },
  design: { id: "design", name: "Riley", title: "Design Lead" },
};

export const relationships = [
  org.manager("ceo", ["design"]),
];
import { OrgChart } from "@memoir/tree";
import "@memoir/tree/styles.css";
import { people, relationships } from "./org-data";
import { OrgCard } from "./org-card";

export default function TeamPage() {
  return <OrgChart people={people} root="ceo" relationships={relationships} card={OrgCard} />;
}

For production org data, use graph mode so every reporting edge can keep a stable ID:

export const graph = {
  people,
  root: "ceo",
  reportingLinks: [
    { id: "ceo-design", managerId: "ceo", reportId: "design", relation: "manager", status: "current", order: 1 },
  ],
};
<OrgChart graph={graph} card={OrgCard} />

Checklist

  • Import @memoir/tree/styles.css once if you want the default skin.
  • Pass people, relationship facts, and a root ID (subject or root).
  • Prefer graph for production family data with multiple unions, guardianship, or per-parent lineage.
  • Prefer graph for production org data when reporting edges need stable IDs or per-report ordering.
  • Update graph state immutably when adding people or relationships, then re-render the same tree component.
  • Use card or renderCard when default cards are too simple.
  • Keep custom card roots spreading the supplied props.
  • Keep selection and editing state in your app.
  • Keep relationship persistence and validation in your app.
  • Use data-tree-drag-ignore on custom card elements that should not start pan dragging.

On this page