PParable
All components

Force Graph

Visual Effects

A 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.json

Runs 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">

PropTypeDefaultDescription
nodes*ForceGraphNode[]Graph nodes. Prefer a stable (memoised) array.
links*ForceGraphLink[]Graph links, referencing node ids. Unknown ids are ignored.
groupColorsRecord<string, string>Explicit colour per group key, e.g. `{ core: "#8b5cf6" }`. Accepts any CSS colour or a `var(--token, fallback)` expression.
groupPalettestring[]DEFAULT_GROUP_PALETTEFallback palette cycled through groups (in first-appearance order) that have no `groupColors` entry.
nodeColorstring"var(--primary, #8b5cf6)"Colour for ungrouped nodes. Defaults to the theme `--primary` token.
linkColorstring"var(--muted-foreground, #9ca3af)"Edge colour. Defaults to the theme `--muted-foreground` token.
labelColorstring"var(--muted-foreground, #9ca3af)"Label colour. Defaults to the theme `--muted-foreground` token.
pulsebooleantrueShow a pulsing ring on `emphasis` nodes (static ring under reduced motion).
nodeRadiusnumber5Base node radius in px (grows slightly with degree and emphasis).
linkDistancenumberLink rest length in px. Defaults to ~22% of the shorter canvas edge.
heightnumber | string360Canvas height. Width always fills the container.
seednumber42Layout seed — change it to get a different deterministic arrangement.
canvasClassNamestringClass applied to the canvas element itself.