Port Display & Roles
Ports are what a line ends in and what the user grabs to start a connection. This page covers how they are drawn, how a source differs from a target, how a line stops at the port, and how to keep buttons inside blocks working.
One port, one circle
A port is a single circle. The same circle is the end of a line and the handle the user drags, and its size, outline and colors do not change when a connection appears or disappears. While a gesture is running, only the state-dependent fields from ports.connect.* are applied on top.
If a port is drawn as a circle, the separate handle dot for the same port is hidden, so nothing is drawn twice.
Which ports are drawn
ports.show decides:
false— no circles.trueor'connected'— a circle on every port that has at least one connection. This is the default.'all'— a circle on every declared port that is not hidden, connected or not. Use it when every row of a list must show where a line can be attached.
Ports of a scrolled-out row follow clipToScrollParents as usual. ports.fadeEdge (px) makes a port fade out over that distance towards the visible edge of its scroller, so a port does not pop in and out.
Styles by role
A port's circle is styled in layers, each one overriding the previous:
ports.*— the group style.ports.source/ports.target— by role (connect: 'source' | 'target'on the port). Each is a state-aware style, likeports.ports.connected— ports that have at least one connection.styleon the port descriptor (v-vl-port="{ id, style: { fill } }").ports.sync: 'marker'— the outline of the circle is taken from the marker of the connection, including its hover state.ports.connect.<state>— the gesture state:source,candidate,target,invalid.
The circle gets the classes vl-port--source / vl-port--target, vl-port--connected, vl-port--<state> and the port's own className. The role and connected classes are added when connecting is enabled, ports.show is 'all', or one of ports.source, ports.target, ports.connected is set; otherwise the markup is the same as without these features.
Drawing the port yourself
ports.render: 'svg'— the default: the library draws the circle.ports.render: 'dom'— the library draws no circle and no handle. The port is your own element (v-vl-porttarget, usually a small<span>in the row). The library starts the gesture from it, setsdata-vl-connect-stateon it, setsaria-pressed="true"on the source. Needsinteraction.connect.enabled. Line ends are still computed from the port's position.#portslot — in the Vue component, rendered for every connectable port, not only the connected ones. Its scope is{ blockId, portId, point, role, connected, state, hovered };roleis'source' | 'target' | 'both',connectedis the number of connections,stateis'idle'or a gesture state,hoveredsays whether the pointer is over the port's block.stateandhoveredare filled when connecting is enabled orports.showis'all'.ports.slotPointerEvents: falsemakes the slot content transparent to the pointer.
Line stops at the port
By default a line goes to the center of the port, under the circle. To stop it at the edge:
lines.endGap— a number, or{ start, end }, in px: the line ends this far before the port, along the side's normal.ports.clearance— a number in px, or'auto', which is the circle's radius plus its outline width. It is added toendGap.
The port keeps its own position: only the line is shortened. layout events still report the real port point.
Sides by role
ports.sides: { source: 'right', target: 'left' } sets the side by role instead of per port. It applies to ports without their own side, to the end of a connection that fell back to a whole block, and to the preview line of a gesture started from a block.
Ends moved to a fallback block
A connection with an end on a fallback block (Fallback Ends) can mark that end:
lines: { proxied: { start: 'circle', end: { shape: 'diamond', size: 8 } } }proxied.start / proxied.end are marker inputs applied only to the moved end. The block that received the lines gets data-vl-proxy-count="N" with the number of lines moved onto it, so a badge such as "+3" can be drawn in CSS or in the template.
Buttons and links inside blocks
A block can be a connection source or target itself (from: 'block', to: 'block'). Interactive elements inside it must keep working, so a press or a click that starts on an element matching interaction.connect.ignore does not start, finish or cancel a connection. The default selector is:
button, a, input, select, textarea, [contenteditable], [data-vl-ignore]Set your own selector as a string, or false to disable the check. No stopPropagation is needed in your handlers.
Roles of blocks
connect: 'source' | 'target' | 'both' | false on a block is checked when a gesture starts and when it finishes, for from: 'block' and to: 'block' in every mode, including the keyboard and startConnect / completeConnect. A block that sets no role takes it from its ports when every port sets one; otherwise it is 'both'.
Attributes on blocks
For styling a whole row without :has(), the library sets attributes on the block element:
data-vl-connect-state—source,candidate,targetorinvalidwhile a gesture runs (the strongest state of the block or its ports wins).data-vl-hover—pointerwhile the pointer is over the block,linewhile the pointer is over a line that ends in it. Set only wheninteraction.hover,interaction.highlightor connecting is on.data-vl-connecting— on the container while a gesture runs.data-vl-proxy-count— see above.
Attributes are removed when the state ends and when the engine is destroyed.
Line groups and overlapping lines
connection.kindwithlines.kinds: { [kind]: ConnectionStyle }— one style for a group of connections. It sits between the group style and the connection's ownstyle.lines.overlap: 'offset'— a line that coincides with another (same ends and path) is moved aside by 3 px per step (alternating sides, for bezier and straight lines) so both stay visible.'stack'is the default: lines are drawn on top of each other.markers.<position>.units: 'px'— a marker'ssizeis in pixels and does not depend on the line width. The default'stroke'scales with the width.
Gesture feedback
interaction.connect.dimOthers—true(opacity 0.35) or a number: other lines are dimmed while a gesture runs.- The engine event
connect:candidate—{ from, candidate, valid }on every change of the candidate (candidateisnullwhen nothing is under the pointer).connect:startcarriesvia('drag','click','keyboard'or'api'). block:mouseenter/block:mouseleavecarryvia: 'line'when they come from hovering a line that ends in the block. When the pointer itself enters the block, the payload is only{ blockId }.- In Vue,
useVisualLinkerConnect(linker)returns the gesture as refs; see Low-Level Composable.