Flab UIv0.0.2
MobileA2UI

Flow

An interactive node-and-edge canvas for flowcharts, pipelines and dependency graphs. Nodes are plain divs and each edge is one SVG path, so it stays crisp at any zoom and renders on the server; the initial layout is deterministic — every node names its column and row. 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 reposition it. Click a node to toggle its selection, or Tab to it and press Enter. With editable set, the canvas becomes an editor — draw edges between nodes, double-click to add, and delete the selected node — reporting each change through callbacks while the caller owns the data. Each node carries a status string beside its tone dot, so colour is never the only signal. The live version is on the Dependency graph showcase.

api-gateway
Ingress
orders-api
Service
auth-svc
Service
orders-db
PostgreSQL
events-queue
Kafka
100%
HealthyDegradedIdleSync callAsync eventSelected path
const nodes = [  { id: 'gateway', title: 'api-gateway', subtitle: 'Ingress', tone: 'success', status: 'Healthy', column: 0, row: 1 },  { id: 'orders', title: 'orders-api', subtitle: 'Service', tone: 'success', status: 'Healthy', column: 1, row: 0 },  { id: 'auth', title: 'auth-svc', subtitle: 'Service', tone: 'warning', status: 'Degraded', column: 1, row: 2 },  { id: 'orders-db', title: 'orders-db', subtitle: 'PostgreSQL', tone: 'success', status: 'Healthy', column: 2, row: 0, selected: true },  { id: 'events', title: 'events-queue', subtitle: 'Kafka', tone: 'neutral', status: 'Idle', column: 2, row: 2 },];
const edges = [  { from: 'gateway', to: 'orders', label: '1.2k rps', emphasis: true },  { from: 'gateway', to: 'auth' },  { from: 'orders', to: 'orders-db', emphasis: true },  { from: 'orders', to: 'events', variant: 'dashed' },  { from: 'auth', to: 'orders-db' },];
// Drag the background to pan, wheel to zoom, drag a node to move it, click to select.<Flow  nodes={nodes}  edges={edges}  height={360}  ariaLabel="Service dependency graph"  className="rounded-xl border border-hairline"/>

Editing

Set editable and own nodes/edges in state. Flow reports edits through onConnect, onNodeAdd, onNodeDelete, onEdgeDelete and onEdgeChange. Double-click the canvas to add a node, drag between the side handles to connect two nodes, and select a node then use its × control or the Delete key to remove it. Click a link to select it: a small toolbar appears for switching its line style (solid/dashed), arrowheads (none/end/both) and colour, or deleting it. To build your own style UI instead — say, in a side panel — read the selected edge from onEdgeSelect and write back with onEdgeChange.

Double-click to add · drag handles to connect · click a link to style or delete it
ingest
Source
transform
Worker
warehouse
Store
100%
HealthyLiveDraftFlow
function PipelineEditor() {  const [nodes, setNodes] = useState(INITIAL_NODES);  const [edges, setEdges] = useState(INITIAL_EDGES);  const [selectedId, setSelectedId] = useState(null);
  return (    <Flow      nodes={nodes}      edges={edges}      editable      selectedId={selectedId}      onSelectChange={setSelectedId}      onConnect={({ from, to }) => setEdges((e) => [...e, { from, to }])}      onNodeAdd={(point) =>        setNodes((n) => [          ...n,          { id: nextId(), title: 'step', column: 0, row: 0, x: point.x - 88, y: point.y - 28 },        ])      }      onNodeDelete={(id) => {        setNodes((n) => n.filter((node) => node.id !== id));        setEdges((e) => e.filter((edge) => edge.from !== id && edge.to !== id));      }}      onEdgeDelete={(edge) =>        setEdges((e) => e.filter((x) => !(x.from === edge.from && x.to === edge.to)))      }      // The built-in toolbar sends { variant } / { arrow } / { tone } patches.      onEdgeChange={(edge, patch) =>        setEdges((e) =>          e.map((x) => (x.from === edge.from && x.to === edge.to ? { ...x, ...patch } : x)),        )      }    />  );}

Props

PropTypeDefaultNotes
nodesFlowNode[]—Boxes on the canvas, first placed by column and row.
edgesFlowEdge[]—Directed connections between nodes.
heightnumber | string460Viewport height; the canvas pans and zooms within it.
ariaLabelstring'Flow diagram'Accessible name for the whole diagram.
controlsbooleantrueShow the built-in fit / zoom control cluster.
fitOnMountbooleantrueFrame every node inside the viewport once, on mount.
minZoomnumber0.4Lower zoom bound.
maxZoomnumber2Upper zoom bound.
zoomStepnumber0.2Per-step zoom factor for the wheel and buttons.
selectedIdstring | null—Controlled selection. Omit for uncontrolled.
defaultSelectedIdstring | null—Uncontrolled initial selection.
onSelectChange(id: string | null) => void—Fires when selection toggles.
onNodesChange(positions: Record<string, FlowPoint>) => void—Fires after a node drag or keyboard nudge ends.
editablebooleanfalseEnable connection handles, the delete control and double-click to add.
onConnect(c: FlowConnection) => void—Fires when an edge is drawn from one node to another.
onNodeAdd(point: FlowPoint) => void—Fires on double-click of empty canvas, with canvas coordinates.
onNodeDelete(id: string) => void—Fires from the × control or the Delete key on the selected node.
onEdgeDelete(edge: FlowEdge) => void—Fires from the edge toolbar or the Delete key on a selected edge.
onEdgeChange(edge: FlowEdge, patch: Partial<FlowEdge>) => void—Fires from the edge toolbar with a variant/arrow/tone patch; enables the toolbar.
onEdgeSelect(edge: FlowEdge | null) => void—Fires when edge selection changes, for driving a custom style UI.
columnWidthnumber176Node width and initial column pitch, in px.
columnGapnumber72Initial horizontal gap between columns, in px.
rowHeightnumber84Initial vertical pitch between rows, in px.
nodeHeightnumber56Node card height, in px.

FlowNode

PropTypeDefaultNotes
idstring—Stable node id, referenced by edges.
titleReactNode—Primary label.
subtitleReactNode—Quieter second line, e.g. a type or owner.
iconReactNode—Leading glyph, shown in a tone-tinted tile.
toneBadgeTone'neutral'Drives the corner status dot and the icon tile.
statusstring—Textual status, so the dot colour is never the only signal.
columnnumber—Zero-based column (horizontal layer) for the initial layout.
rownumber—Zero-based row within the initial layout grid.
x / ynumber—Explicit initial position in canvas px; overrides column/row when set.
selectedbooleanfalseSelects this node on first render (uncontrolled).

FlowEdge

PropTypeDefaultNotes
fromstring—Source node id.
tostring—Target node id.
labelstring—Short mid-path label, e.g. “1.2k rps”.
variant'solid' | 'dashed''solid'Line style — dashed reads as an async or indirect link.
arrow'end' | 'start' | 'both' | 'none''end'Arrowhead placement; none for an undirected link, both for bidirectional.
toneBadgeTone—Colours the line and its arrow; defaults to a hairline grey.
emphasisbooleanfalseAccent colour and a thicker line for a highlighted path.
idstring—Stable key; falls back to `${from}->${to}`.

FlowLegend

PropTypeDefaultNotes
itemsFlowLegendItem[]—Each names a status dot (tone) or a line style (line) in text.