Blocks & Ports
A block is any DOM element you already control — the engine only measures it (and its ports), and never touches its content or layout beyond an optional drag transform.
Defining a block
BlockDescriptor
id
string
Unique block id, referenced by a connection's from.blockId/to.blockId.
el
HTMLElement
The block's own element.
ports
PortDescriptor[] · default: — (unset)
Explicit ports — see "Defining a port" below. Omitted, connections anchor directly to the block's own border, with side: 'auto'.
draggable
boolean · default: follows blocks.draggable
Overrides the instance-level default for this one block.
dragHandle
string | HTMLElement · default: — (the whole block starts a drag)
A CSS selector, or an element inside el, that starts the drag instead of the whole block.
dragBounds
DragBounds · default: follows blocks.drag.bounds
Overrides the instance-level default for this one block. See "Drag & drop" below for the three accepted shapes.
portSpread
boolean | { gap?: number; padding?: number } · default: follows ports.spread
Spreads the connections that share a port side of this block into separate virtual ports. A port's own spread overrides it. See Port Spreading.
highlightable
boolean · default: follows interaction.highlight
Turns on or off the highlight state of this block's connections while the pointer is over the block; the local value wins over the global one. See Visual States.
Example — every field at once:
linker.setBlocks([
{
id: 'task-1', // unique id — connections reference this via from.blockId/to.blockId
el: taskCardEl, // the block's own element
ports: [{ id: 'output', side: 'right', offset: 0.5 }], // optional, see "Defining a port" below — omit entirely to anchor to the block's own border instead
draggable: true, // this block can be dragged even if blocks.draggable is false
dragHandle: '.card-header', // only this part of the block starts a drag — omit to let the whole block start one
dragBounds: 'container', // this block can never be dragged outside the diagram's own container
portSpread: { gap: 20 }, // connections sharing a side of this block each get their own virtual port, 20px apart
highlightable: true, // pointing at this block highlights its connections, even if interaction.highlight is false
},
])Defining a port
PortDescriptor
A block's ports array lets a connection anchor somewhere more specific than the block's own border — a particular row in a list, a specific field in a form.
id
string
Port id, referenced by a connection endpoint's portId.
target
string | HTMLElement · default: — (the block's own element)
A CSS selector (resolved against the block's el) or a direct element — where the port's connector point actually sits.
side
PortSide | FixedSide[] · default: 'auto'
'auto' resolves the exit/entry side from the other endpoint's position on every render, picking whichever side best faces it. A FixedSide[] (a subset of 'top' | 'right' | 'bottom' | 'left') narrows that automatic choice — e.g. ['left', 'right'] to rule out top/bottom exits entirely. A single fixed side pins it outright.
offset
number · default: 0.5
Position along the resolved side, 0..1 (0.5 is centered). Ignored when anchorBlockId/anchorEl is set.
anchorBlockId
string · default: — (unset)
Renders this port's connector point on another registered block's own border instead of target's — useful when target is a child nested well inside a container (e.g. a row inside a group block) but the connection should visually leave from the container's own edge. The point still tracks target's actual position, projected onto the anchor block's border, so siblings anchored to the same block keep their relative order instead of collapsing to one spot. Side resolution (including 'auto') is based on the anchor block, not target. Ignored when anchorEl is also set.
anchorEl
HTMLElement · default: — (unset)
Like anchorBlockId, but anchors directly to a given element instead of a registered block's own el — for anchoring to an element that isn't (or isn't yet) one of the engine's registered blocks. Takes precedence over anchorBlockId when both are set.
spread
boolean | { gap?: number; padding?: number } · default: follows the block's portSpread
Spreads the connections that share this port's side. false opts the port out even when its block spreads. See Port Spreading.
Example — every field at once:
{
id: 'output', // referenced by a connection's from.portId/to.portId
target: '.output-row', // a CSS selector inside the block — omit to use the block's own element
side: ['right', 'bottom'], // 'auto' can only ever pick right or bottom here, never top/left
offset: 0.25, // ignored in this example, since anchorBlockId below overrides where the point actually sits
anchorBlockId: 'group-1', // the point is drawn on block "group-1"'s own border, while still tracking target's real position
spread: true, // connections sharing this port's side get their own virtual ports
}Setting anchorEl: someElement instead of anchorBlockId anchors to that exact element rather than a registered block — and wins if both happen to be set at once.
Drag & drop
Any block can be made draggable — set blocks.draggable: true in the configuration for every block, or draggable on a single block (BlockDescriptor.draggable, wins when set). Dragging is pointer-driven, moves the block through the standalone CSS translate property — so it composes with any transform the block already has instead of overwriting it — and every incident connection re-routes in real time as the block moves, no manual re-render call needed.
const linker = createVisualLinker(diagramEl, {
blocks: {
draggable: true, // every block can be dragged unless it sets draggable: false itself
drag: {
grid: 20, // dragged blocks snap to a shared 20px grid, so independently moved blocks still line up
bounds: 'container', // no block can be dragged outside the diagram's own container
},
},
})
linker.setBlocks([
{
id: 'task-1', // this block's id
el: taskCardEl, // this block's own element
dragHandle: '.card-header', // only the header starts a drag — the rest of the card is inert
},
{
id: 'task-2', // this block's id
el: otherCardEl, // this block's own element
draggable: false, // opts this one block out, even though blocks.draggable above is true
},
])dragBounds (for every block via blocks.drag.bounds, or per block via BlockDescriptor.dragBounds) confines a draggable block's position, clamped so the block's own rect never leaves the bounds:
'container'— the engine's own container element, as used above.- an
HTMLElement— an arbitrary element's box (e.g. a dedicated drop-zone elsewhere in the layout, not necessarily the container itself). - a
DragBoundsInsetobject ({ top?, right?, bottom?, left? }, in px, unset edges default to0) — the container's own box, shrunk by these paddings, e.g.{ top: 16, bottom: 16 }to keep dragged blocks clear of a header/footer overlapping the container.
Unset on both levels — unconstrained, the default behavior.
Drag events (block:dragstart/block:drag/block:dragend) are covered on the Engine API page.