Official React components for rendering interactive family trees with relationship-aware connectors. Data fetching and persistence are intentionally left to the host app.
bunx jsr add @4everlabs/tree- React 18 or 19.
- Tailwind classes are used for styling. Define the design tokens referenced by the library (examples below).
import { FamilyTree } from "@4everlabs/tree";
import type { FamilyMember, AddMemberPayload } from "@4everlabs/tree";
const rootMember: FamilyMember = {
id: "root",
name: "Alex Johnson",
status: "linked",
parents: [],
siblings: [],
children: [],
};
export function FamilyTreePage() {
const handleAddMember = async (payload: AddMemberPayload) => {
// Persist to your backend, then update your `rootMember` tree.
console.log("Add member", payload);
};
return (
<FamilyTree
rootMember={rootMember}
canEdit
onAddMember={handleAddMember}
searchProfiles={async (query) => {
// Return your own search results
return [];
}}
onNavigateProfile={(member, target) => {
// Route using your app router
console.log("Navigate", member, target);
}}
resolveAvatarUrl={(url) => url || ""}
designPreset="contrast"
/>
);
}The tree is derived from nested arrays on FamilyMember:
parents: Direct parents for the member.siblings: Direct siblings for the member.children: Direct children for the member.spouse: The member’s partner. The layout supports a single spouse per node.
The relation field is used for labels only. Layout is driven by the relationship arrays above.
Connector placement is deterministic and consistent across presets:
spouse: Partner line connects at mid‑height on each card’s inner edge (left/right), inset byanchors.coupleInsetPx.parent → child(couple): A junction is placed at the midpoint of the couple line. A vertical trunk drops to a horizontal sibling bus above the children, and each child connects from the top center down to that bus.parent → child(single parent): The junction is the bottom center of the parent card; trunk + bus + child drops apply.siblings: Siblings connect through the shared sibling bus above them; each sibling drops from its top center to the bus.
Ordering is based on the array order you provide. Siblings are split left and right around the root member based on list order.
When canEdit is true, the UI emits an AddMemberPayload via onAddMember:
type: "existing": user selected fromsearchProfilesresults.type: "manual": manual entry (name, optional birthday).type: "invite": invite by email.
You are responsible for persistence and updating the rootMember tree once the add completes.
Use relationOptions to control which relationship types appear in the add menu:
<FamilyTree
rootMember={rootMember}
canEdit
onAddMember={handleAddMember}
relationOptions={(member, isRoot) =>
isRoot ? ["parent", "sibling", "spouse", "child"] : ["child"]
}
/>Connector styling is controlled by presets or overrides:
designPreset:default,compact,contrast.designOverrides: partial override ofFamilyTreeConnectorConfig.
Example override:
import { getFamilyTreeConfig } from "@4everlabs/tree";
const config = getFamilyTreeConfig("compact", {
statusColors: {
linked: "bg-emerald-500",
},
anchors: {
verticalGapPx: 32,
},
});If you want to render your own card UI while keeping layout and connectors, provide renderNode:
import type { FamilyMember, FamilyTreeRenderNodeOptions } from "@4everlabs/tree";
const renderNode = (member: FamilyMember, options: FamilyTreeRenderNodeOptions) => (
<div className="custom-card">
<div>{member.name}</div>
{options.canEdit ? (
<button onClick={() => options.onAddMember("child", member.id)}>
Add child
</button>
) : null}
</div>
);
<FamilyTree rootMember={rootMember} renderNode={renderNode} />;Use these props to integrate with your page layout:
className: applied to the outermost wrapper.containerClassName: applied to the padded inner container.titleClassName: applied to the title.showTitle: setfalseto hide the header.
The UI uses Tailwind class tokens you must define in your design system:
bg-canvas-base,bg-canvas-muted,bg-bgtext-copy-primary,text-copy-secondary,text-copy-muted,text-copy-disabledborder-stroke-default,border-stroke-mutedbg-stroke-defaulttype-display-lg,type-heading-md,type-caption
If you don’t use these names, map them to your own tokens or replace classes at build time.
rootMember(required): Root of the tree.title: Title text. Defaults toFamily Tree.showTitle: Hide or show the title header.className: Outer wrapper class.containerClassName: Inner container class.titleClassName: Title class.canEdit: Enables add‑member UI.onAddMember: Called withAddMemberPayloadwhen a member is added.searchProfiles: Async search provider for the dialog.onNavigateProfile: Called when a card with a profile ID or slug is clicked.resolveAvatarUrl: Optional URL resolver for avatar images.designPreset: Connector preset (default,compact,contrast).designOverrides: Override connector sizing and colors.relationOptions: Restrict add‑member relation choices.renderNode: Render your own card UI.
id: Unique string identifier.name: Display name.birthday: Optional display string.avatarUrl: Optional avatar URL.relation: Optional label for the card.status:linked,manual, orinvite_pending(affects connector color).profileId/profileSlug: Used for navigation targets.parents,siblings,children,spouse: Relationship arrays driving layout.
type: "existing":{ relation, parentId, name, profileId, avatarUrl }type: "manual":{ relation, parentId, name, birthday? }type: "invite":{ relation, parentId, firstName, lastName?, email }
MIT