Force Graph
Visual EffectsA canvas force-directed graph with a seeded deterministic layout: nodes repel, links act as springs, and the simulation cools until it settles and stops ticking entirely. Hover or keyboard-focus highlights a node's neighbourhood with per-group theme-aware colours and pulse rings on emphasis nodes. Pre-settles synchronously to a static frame under prefers-reduced-motion.
Inspired by Extracted from the Redoubt template — independently built, not affiliated. Attribution policy
- gateway (core)
- auth (core)
- billing (core)
- worker (core)
- mailer (core)
- postgres (data)
- redis (data)
- queue (data)
- search (data)
- web (client)
- ios (client)
- android (client)
Installation
npx shadcn@latest add https://parable-three.vercel.app/r/force-graph.jsonRuns the shadcn CLI — the component source is copied intocomponents/parable/force-graph.tsx. You own it from there.
Usage
The exact code rendering the preview above.
<ForceGraph
height={300}
seed={7}
nodes={[
{ id: "gateway", label: "gateway", group: "core", emphasis: true },
{ id: "auth", label: "auth", group: "core" },
{ id: "billing", label: "billing", group: "core" },
{ id: "worker", label: "worker", group: "core" },
{ id: "mailer", label: "mailer", group: "core" },
{ id: "postgres", label: "postgres", group: "data", emphasis: true },
{ id: "redis", label: "redis", group: "data" },
{ id: "queue", label: "queue", group: "data" },
{ id: "search", label: "search", group: "data" },
{ id: "web", label: "web", group: "client" },
{ id: "ios", label: "ios", group: "client" },
{ id: "android", label: "android", group: "client" },
]}
links={[
{ source: "web", target: "gateway" },
{ source: "ios", target: "gateway" },
{ source: "android", target: "gateway" },
{ source: "gateway", target: "auth" },
{ source: "gateway", target: "billing" },
{ source: "gateway", target: "search" },
{ source: "auth", target: "postgres" },
{ source: "auth", target: "redis" },
{ source: "billing", target: "postgres" },
{ source: "billing", target: "queue" },
{ source: "queue", target: "worker" },
{ source: "worker", target: "mailer" },
{ source: "worker", target: "postgres" },
{ source: "search", target: "postgres" },
]}
/>Props
ForceGraphProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">
| Prop | Type | Default | Description |
|---|---|---|---|
| nodes* | ForceGraphNode[] | — | Graph nodes. Prefer a stable (memoised) array. |
| links* | ForceGraphLink[] | — | Graph links, referencing node ids. Unknown ids are ignored. |
| groupColors | Record<string, string> | — | Explicit colour per group key, e.g. `{ core: "#8b5cf6" }`. Accepts any CSS colour or a `var(--token, fallback)` expression. |
| groupPalette | string[] | DEFAULT_GROUP_PALETTE | Fallback palette cycled through groups (in first-appearance order) that have no `groupColors` entry. |
| nodeColor | string | "var(--primary, #8b5cf6)" | Colour for ungrouped nodes. Defaults to the theme `--primary` token. |
| linkColor | string | "var(--muted-foreground, #9ca3af)" | Edge colour. Defaults to the theme `--muted-foreground` token. |
| labelColor | string | "var(--muted-foreground, #9ca3af)" | Label colour. Defaults to the theme `--muted-foreground` token. |
| pulse | boolean | true | Show a pulsing ring on `emphasis` nodes (static ring under reduced motion). |
| nodeRadius | number | 5 | Base node radius in px (grows slightly with degree and emphasis). |
| linkDistance | number | — | Link rest length in px. Defaults to ~22% of the shorter canvas edge. |
| height | number | string | 360 | Canvas height. Width always fills the container. |
| seed | number | 42 | Layout seed — change it to get a different deterministic arrangement. |
| canvasClassName | string | — | Class applied to the canvas element itself. |