Flab UIv0.0.2
MobileA2UI

Knowledge graph

A force-directed relationship graph in the spirit of Obsidian's graph view. Nodes are circles sized by how connected they are, links are thin SVG lines, and a small physics simulation lays the web out — linked nodes pull together, every node pushes the rest away, and a gentle gravity keeps the cloud centred. The layout is seeded and settled synchronously, so the first server paint is stable and hydration never mismatches. From there the viewport is live: drag the background to pan, wheel or the control cluster to zoom, fit to frame everything, and drag a node to pull it around. Hovering or selecting a node lifts it and its neighbours out of the crowd while the rest dim back, so a dense web stays legible. Every node is a real button — Tab to it, Enter to select, arrow keys to nudge — and carries a text label, so colour is never the only signal. The live version is on the Knowledge graph showcase.

100%
MapsProjectsResearchWritingPeopleDaily
const nodes = [  { id: 'home', label: 'Home', group: 'Maps' },  { id: 'graph-view', label: 'Graph view', group: 'Projects' },  { id: 'flab-ui', label: 'Flab UI', group: 'Projects' },  { id: 'force', label: 'Force layout', group: 'Research' },  { id: 'graph-theory', label: 'Graph theory', group: 'Research' },  { id: 'essay', label: 'On graphs', group: 'Writing' },];
const links = [  { source: 'home', target: 'graph-view' },  { source: 'home', target: 'flab-ui' },  { source: 'flab-ui', target: 'graph-view' },  { source: 'graph-view', target: 'force' },  { source: 'graph-view', target: 'graph-theory' },  { source: 'graph-view', target: 'essay' },  { source: 'force', target: 'graph-theory' },];
// Drag the background to pan, wheel to zoom, drag a node to move it, click to select.// The graph is monochrome by default; pass colorByGroup to tint nodes by folder.<KnowledgeGraph  nodes={nodes}  links={links}  height={360}  colorByGroup  ariaLabel="Vault knowledge graph"  className="rounded-xl border border-hairline"/>

Highlighting a search

Pass a set of node ids to highlightIds and the graph lights each one and its direct neighbours while the rest dim back — the links touching a highlighted node turn accent. It is a persistent emphasis, independent of selection, so it pairs neatly with a search box: filter your nodes to the matches and hand their ids to the graph. Hovering a node still takes over while the pointer is on it.

3
100%
function SearchableGraph() {  const [query, setQuery] = useState('graph');
  const highlightIds = useMemo(    () =>      nodes        .filter((n) => n.label.toLowerCase().includes(query.trim().toLowerCase()))        .map((n) => n.id),    [query],  );
  return (    <>      <SearchInput value={query} onChange={(e) => setQuery(e.target.value)} />      <KnowledgeGraph nodes={nodes} links={links} highlightIds={highlightIds} />    </>  );}

Props

PropTypeDefaultNotes
nodesKnowledgeGraphNode[]—The notes/entities on the canvas.
linksKnowledgeGraphLink[]—Undirected connections between nodes, by id.
heightnumber | string460Viewport height; the canvas pans and zooms within it.
ariaLabelstring'Knowledge graph'Accessible name for the whole graph.
nodeRadiusnumber6Base node radius in px; scaled up by link count or value.
linkDistancenumber60Target distance between two linked nodes, in px.
chargenumber-240Repulsion strength between nodes; more negative pushes harder.
showLabelsbooleantrueShow a text label beside each node.
colorByGroupbooleanfalseTint nodes by their group. Off by default: the graph stays monochrome and colour comes only from an explicit node tone or from selection/hover emphasis.
labelZoomThresholdnumber0.7Below this zoom, labels hide except for the emphasised node, so a zoomed-out overview stays clean. Set to 0 to always show labels.
controlsbooleantrueShow the built-in fit / zoom control cluster.
fitOnMountbooleantrueFrame every node inside the viewport once, on mount.
minZoomnumber0.2Lower zoom bound.
maxZoomnumber3Upper zoom bound.
zoomStepnumber0.2Per-step zoom factor for the wheel and buttons.
seednumber1Seed for the deterministic initial layout.
highlightIdsstring[]—Node ids to highlight (e.g. a search result): each and its direct neighbours stay lit while the rest dim. Independent of selection.
selectedIdstring | null—Controlled selection. Omit for uncontrolled.
defaultSelectedIdstring | null—Uncontrolled initial selection.
onSelectChange(id: string | null) => void—Fires when selection toggles.
toneForGroup(group: string) => BadgeTone—Map a group name to a tone; else a stable tone is assigned per group.

KnowledgeGraphNode

PropTypeDefaultNotes
idstring—Stable node id, referenced by links.
labelstring—Display label; falls back to the id. Doubles as the accessible name.
groupstring—Grouping key; drives the node colour and its legend swatch.
toneBadgeTone—Explicit tone; overrides the tone derived from group.
valuenumber—Manual size weight; when omitted, the node is sized by its link count.
x / ynumber—Optional fixed initial position in world px; else it is seeded.
selectedbooleanfalseSelects this node on first render (uncontrolled).

KnowledgeGraphLink

PropTypeDefaultNotes
sourcestring—Source node id.
targetstring—Target node id.
strengthnumber0.7Optional weight, 0–1, scaling how strongly the link pulls.

KnowledgeGraphLegend

PropTypeDefaultNotes
itemsKnowledgeGraphLegendItem[]—Each pairs a group colour (tone) with its meaning in text.