DiagramEditor Composition
Draws and edits a diagram of nodes joined by arrows. The caller owns the nodes and edges; every change is a request the caller applies, or does not.
Usage
Drag a kind in, or tap it. Drag or click between handles to connect; from the keyboard, C then C. F2 renames.
- Draft
- → Review
- Review
- → Approved: Yes
- → Rework: No
- Approved
- Rework
Interaction contract
The editor runs on the React Flow runtime (@xyflow/react) and keeps its interaction model rather than
re-implementing one. Nodes and edges are focusable; Enter or Space selects the focused one, arrow keys
move selected nodes one grid cell, and Delete or Backspace asks to remove the selection. With a pointer,
drag a node to move it, drag from one handle to another to connect — or click one handle and then another,
which works on touch — and drag the background to pan. Scroll or a two-finger swipe pans (Shift + scroll
pans sideways); pinch, Ctrl/⌘ + scroll and the − / + / fit controls zoom — a wheel that zoomed fought the
page and every trackpad habit. The read-only Diagram does the same.
Adding a node from the palette
palette lists the kinds a person can add, each drawn in its node shape with its description as a
tooltip. Drag one onto the canvas and onAddNode({ kind, position }) asks for it centred where it was
dropped, in whole cells. A drag is not the only way: a tap, a click or Enter asks for it in the middle of
what is visible, so touch screens and keyboards can add nodes too (WCAG 2.5.7). Like every change it is a
request — the node appears when you add it to nodes.
Renaming in place
Set editingId to a node's id and it shows an input where its label was — set it to the id of a node you
just added, so a new node is named where it sits. Enter or leaving the input calls
onRenameSubmit(id, label); Escape, or an empty or unchanged name, calls onRenameCancel. F2 still
reports onRename({ kind, id }), so a caller can start the same inline rename from the keyboard.
Nothing is applied by the component. A move is reported in whole cells when the drag ends or the key is
pressed, and snaps back unless the caller updates nodes; a connection, a removal (a node brings the edges
touching it) and a rename are requests too. The component does not know what a valid diagram is.
React Flow has no keyboard way to connect, so this component adds one: C on a focused node begins a
connection and C on another node commits it, announced through a status message; C on the starting
node or Escape cancels. F2 asks to rename the focused node or edge — the caller renders the input.
A selected node says what it is through renderInspector: the editor hands it the selection described in
a person's terms — the node with where it leads and what leads to it, or an edge with both ends — and draws
the consumer's panel beside the canvas, or below it on a narrow screen. Put the actions there as real
buttons (rename, connect, remove); the shortcuts stay as accelerators, and on a phone the panel is the only
way to act. The diagram opens fitted, but never below a readable zoom; labels wrap to two lines with the full
text as a tooltip; legendLabels names the node shapes in use.
Do
Keep nodes and edges in state and apply each request, rejecting the ones your rules forbid.
Don't
Treat onRemove as already done, or put interactive controls inside a node's content.
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
| label | string | — | Accessible name for the editor viewport. |
| nodes | DiagramNode[] | — | id, label, position in grid cells, optional shape, tone and static content. |
| edges | DiagramEdge[] | — | id, from, to, optional label and tone. |
| selectedId | string | null | — | The selected node. |
| onSelectionChange | (id) => void | — | Called when a different node becomes selected. |
| onMoveNode | (id, position) => void | — | A move request in whole cells, from a drag or an arrow key. |
| onConnect | (from, to) => void | — | A connection request. Never called with from equal to to. |
| onRemove | ({ nodeIds, edgeIds }) => void | — | A removal request; a node brings the edges touching it. |
| onRename | ({ kind, id }) => void | — | A rename request for a node or an edge. |
| connectingStatus | (nodeLabel) => string | — | Localised announcement while connecting from the keyboard. |
| hint | string | — | Optional visible guidance above the viewport. |
| outlineLabel | string | Diagram outline | Accessible name for the outline list. |
| ariaLabels | Partial<AriaLabelConfig> | — | Localised names and keyboard descriptions for the runtime. |
| renderInspector | (selection: DiagramSelection | null) => ReactNode | — | The panel that says what is selected and offers its actions as buttons. null when nothing is selected. |
| inspectorLabel | string | Selection | Accessible name for the inspector panel. |
| legendLabels | Partial<Record<DiagramShape, string>> | — | Names for the node shapes; given, a legend lists the shapes in use. |
| palette | DiagramPaletteItem[] | — | { kind, label, shape, tone?, description? } — the kinds a person can drag or tap in. |
| onAddNode | ({ kind, position }) => void | — | An add request, centred on the drop point or the visible middle, in whole cells. |
| paletteLabel | string | Add a node | Accessible name for the palette. |
| paletteHint | string | — | One line shown in the palette. |
| editingId | string | null | — | The node renamed in place. |
| onRenameSubmit | (id, label) => void | — | The new name, trimmed, on Enter or leaving the input. |
| onRenameCancel | () => void | — | Escape, or an empty or unchanged name. |
| renameLabel | (label) => string | `Rename ${label}` | Accessible name for the rename input. |
Skeleton
DiagramEditor.Skeleton is the same loading state as Diagram.Skeleton.
Keyboard
| Key | Result |
|---|---|
Tab | Moves between nodes, edges and the controls |
Enter / Space | Selects the focused node or edge |
| Arrow keys | Request moving the selected nodes one grid cell |
Delete / Backspace | Request removing the selection, with the edges touching removed nodes |
C | Begin connecting from the focused node; while connecting, connect to it |
Escape | Cancel connecting; clear the selection |
F2 | Request renaming the focused node or edge |
Tokens
--cb-space-5 (one grid cell), --cb-canvas-block-size, --cb-surface, --cb-border*, --cb-tone-*,
--cb-fg*, --cb-focus-*, --cb-radius-* and --cb-text-*.