Skip to content

Linked Block

The Linked Block component provides draggable/resizable blocks with link points on each side that can be connected with visual connectors (lines/curves/zigzags). It is useful for building simple node/link editors, flow diagrams, or block schematics directly in the browser.

This component relies on the following Metro UI modules (imported via its index.js):

  • draggable
  • resizable
  • linked-connector
<!-- A simple linked block with default settings -->
<div data-role="linked-block" style="top: 100px; left: 100px; position: absolute;">
My Block
</div>
  • The component will ensure an id on the element (if missing), add required inner structure, and wrap your content into .linked-block-content.
  • Each block contains four sides: .north-side, .east-side, .south-side, .west-side.
  • When showAddButtons is enabled (default), small “+” buttons appear on hover to add a new link point on a side and start a connection.
// Create a block programmatically
const block = Metro.linkedBlock.create({
container: "#container", // where to place the block (defaults to body)
id: "block-1",
top: 100,
left: 100,
content: "Block 1",
canDrag: true,
canResize: true
});
// Add a link point to a specific side
block.addPoint("north"); // sides: north | east | south | west
// Create another block and connect them
const block2 = Metro.linkedBlock.create({
container: "#container",
id: "block-2",
top: 100,
left: 300,
content: "Block 2",
canResize: true
});
block2.addPoint("north");
// Connect existing points (first available by default)
block.connect(block2.element, { type: "zigzag" });

Programmatic Initialization on Existing Element

Section titled “Programmatic Initialization on Existing Element”
Metro.makePlugin("#myBlock", "linked-block", {
canResize: true,
showAddButtons: true
});
const inst = Metro.getPlugin("#myBlock", "linked-block");
<script>
// Linked Block global setup
window.metroLinkedBlockSetup = {
resizeHotkey: "alt+t"
};
// Connector global setup (from linked-connector)
window.metroConnectorSetup = {
deleteButton: true, // show delete button when a connector path is selected
// type: 'zigzag' // default connector type: line | curve | zigzag
};
</script>
ParameterTypeDefaultDescription
widthstring|number|nullnullInitial width (CSS value).
heightstring|number|nullnullInitial height (CSS value).
minWidthnumber0Minimum block width when resizing.
minHeightnumber0Minimum block height when resizing.
maxWidthnumber|nullnullMaximum block width when resizing.
maxHeightnumber|nullnullMaximum block height when resizing.
contentstring""HTML content for the block body. If the element already has innerHTML, it will be used instead.
showAddButtonsbooleantrueShows ”+” hover buttons on block sides to add points and start connections.
addButtonsstring”north east south west”Sides where add buttons appear (space-separated): any of north, east, south, west.
resizeHotkeystring|nullnullHotkey to toggle resize availability on the active block (e.g., "alt+t").
dragHotkeystring|nullnullHotkey to toggle drag availability on the active block (e.g., "alt+d").
connectionType”line”|“curve”|“zigzag""curve”Default connector type when connecting interactively.
connectionStyle”solid”|“dashed”|“dotted""solid”Default line style for interactive connections.
onePointbooleanfalseIf true, points are centered (useful for single-point blocks).
canDragbooleantrueEnables dragging for the block.
canResizebooleantrueEnables resizing for the block.
onAddPointfunctionMetro.noop(pointEl, side, pointId, blockEl) — called when a point is added.
onRemovePointfunctionMetro.noop(pointEl, side, pointId, blockEl) — called when a point is removed.
onStartConnectionfunctionMetro.noop(buttonEl, side) — called when interactive connecting starts.
onCancelConnectionfunctionMetro.noop() — called if an interactive connect is canceled.
onConnectfunctionMetro.noopSee Events section for signatures (interactive vs direct API).
onDisconnectfunctionMetro.noop(connectionId) — called when a connection is removed.
onDragStartfunctionMetro.noop({top, left}, element) — when dragging starts.
onDragMovefunctionMetro.noop({top, left}, element) — while dragging.
onDragEndfunctionMetro.noop({top, left}, element) — when dragging stops.
onResizefunctionMetro.noop({width, height}, element) — when block is resized.
onLinkedBlockCreatefunctionMetro.noop(element) — when the component is created.

Data attribute usage (dashed format) examples:

  • data-role="linked-block"
  • data-can-resize="true"
  • data-can-drag="true"
  • data-show-add-buttons="false"
  • data-add-buttons="north east"
  • data-connection-type="curve"
  • data-connection-style="dashed"
  • data-one-point="true"
  • data-resize-hotkey="alt+t"
  • data-drag-hotkey="alt+d"
  • data-on-add-point="onAddPointHandler"

You can handle events using options (callbacks) or data-on-* attributes.

EventDescription
onLinkedBlockCreateFired when the linked block is created. Signature: (element).
onAddPointFired after a new link point has been added. Signature: (pointEl, side, pointId, blockEl).
onRemovePointFired after a link point has been removed. Signature: (pointEl, side, pointId, blockEl).
onStartConnectionFired when interactive connection starts. Signature: (buttonEl, side).
onCancelConnectionFired when an interactive connection is canceled. Signature: ().
onConnectFired when a connection is created. Interactive: (connectionId, sourceBlockEl, targetBlockEl, sourcePointEl, targetPointEl, connectorInst). Direct connect() API: (targetEl, sourcePointEl, targetPointEl, connectorEl).
onDisconnectFired when an existing connection is removed. Signature: (connectionId).
onDragStartWhen dragging starts. Signature: ({top, left}, element).
onDragMoveWhile dragging, with updated position. Signature: ({top, left}, element).
onDragEndWhen dragging stops. Signature: ({top, left}, element).
onResizeWhen block is resized (if canResize: true). Signature: ({width, height}, element).

Example (with data attributes):

<div
id="myBlock"
data-role="linked-block"
data-resizable="true"
data-resize-hotkey="alt+t"
data-on-add-point="onAddPoint"
data-on-connect="onConnect">
</div>
<script>
function onAddPoint(point, side, id, block) {
console.log("Point added:", side, id);
}
function onConnect(targetOrSource, a, b, connector) {
console.log("Connected:", connector);
}
</script>
  • addPoint(side) — Adds a new link point to the specified side ("north"|"east"|"south"|"west"). Returns the created point element (Dom).
  • removePoint(pointId) — Removes a specific point by id. Also disconnects any connectors attached to that point. Returns boolean.
  • getPoints(side?) — Returns a collection of points for a side or for all sides.
  • connect(targetBlock, options = {}) — Connects the current block to another. Options:
  • type: "line"|"curve"|"zigzag" (default "curve")
  • container: SVG container parent (defaults to the parent of the block)
  • sourcePoint / targetPoint: specific point elements to connect (optional) Returns { id, connector, sourcePoint, targetPoint } or null.
  • disconnect(connectionId) — Removes a specific connection by id. Returns boolean.
  • disconnectPoint(pointId) — Removes all connections related to a given point id. Returns number of removed connections.
  • disconnectAll() — Removes all connections of the block. Returns number of removed connections.
  • setContent(html) — Sets block content.
  • getContent() — Gets current block content HTML.
  • getConnections() — Returns an array of connection objects registered on the block.
  • destroy() — Destroys the component, removes event handlers and all connections.

Static helper:

  • Metro.linkedBlock.create(options) — Programmatically create and initialize a block. Options include: id, container, content, top, left, draggable, resizable, etc. Returns the plugin instance.

Example of method usage:

const b1 = Metro.linkedBlock.create({ container: "#container", content: "A", top: 120, left: 100 });
const b2 = Metro.linkedBlock.create({ container: "#container", content: "B", top: 120, left: 320 });
b1.addPoint("north");
b2.addPoint("north");
const c = b1.connect(b2.element, { type: "zigzag" });
console.log(c.id);
VariableDefault (Light)Dark ModeDescription
--linked-block-point-size7px7pxDiameter of link point circles.
--linked-block-point-color#b1b1b1#656a70Point fill color.
--linked-block-line-width1px1pxStroke width for connector paths.
--linked-block-min-size100px100pxDefault block min size (used for initial width/height).
--linked-block-border-radius4px4pxBorder radius for blocks.
--linked-block-backgroundvar(--default-background)var(--default-background)Block background.
--linked-block-color#191919#ffffffText color.
--linked-block-border-color#b1b1b1#656a70Block border color.
--linked-block-line-color#b1b1b1#656a70Default connector path color.
--linked-block-border-color-active#007bff#007bffBorder color when a block is active/selected.
#container {
--linked-block-point-color: #ff0000;
--linked-block-line-width: 2px;
}
  • .linked-block — Root element of the component; positioned absolutely.
  • .linked-block-content — Inner content container (fills the block with inset: 4px).
  • .north-side, .east-side, .south-side, .west-side — Side containers that hold .link-point circles.
  • .link-point — Point element used as connection endpoints.
  • .add-point-btn — Small ”+” button (shown on hover) to add a point and start a connection.
  • .add-point-north | .add-point-east | .add-point-south | .add-point-west — Button positions for each side.
  • .connecting — Visual state while a connection is in progress.
  • .active-block — Applied to a block when clicked; highlights its border.
  • [data-can-resize=true] — When true, shows resizer handles (together with resizable: true).

The Linked Connector component renders SVG connectors (lines, curves, zigzags) between two link points. It is used internally by Linked Block, but can also be used independently to connect any two DOM points/elements and keep the connection updated while blocks move.

None.

// Suppose you have two link-point elements (e.g., created by Linked Block)
const pA = document.querySelector('#block-1 .link-point');
const pB = document.querySelector('#block-2 .link-point');
// Create a connector between them
const conn = Metro.connector.create(pA, pB, {
type: 'curve', // 'line' | 'curve' | 'zigzag'
lineStyle: 'solid', // 'solid' | 'dashed' | 'dotted'
deleteButton: true, // show delete icon when a connector is selected
arrow: false // draw an arrowhead at the end
});
// Access instance methods
conn.setType('zigzag');
conn.setLineStyle('dashed');
conn.update(); // manually update if needed
<div id="myConnector"></div>
<script>
const pA = document.querySelector('#block-1 .link-point');
const pB = document.querySelector('#block-2 .link-point');
Metro.makePlugin('#myConnector', 'connector', {
pointA: pA,
pointB: pB,
type: 'line',
lineStyle: 'dotted'
});
const inst = Metro.getPlugin('#myConnector', 'connector');
</script>
<script>
window.metroConnectorSetup = {
deleteButton: true,
lineStyle: 'solid'
};
</script>
ParameterTypeDefaultDescription
pointAElement|Dom|nullnullFirst endpoint element (e.g., a .link-point).
pointBElement|Dom|nullnullSecond endpoint element.
type’line’|‘curve’|‘zigzag''curve’Connector shape type.
containerElement|Dom|Selector|nullnullSVG container parent. If not set, the connector will use the parent of the plugin element.
autoUpdatebooleantrueAutomatically update the connector on block moves/resizes.
idstring|nullautoConnection id. Generated if not provided (connector-<uid>).
deleteButtonbooleanfalseShow delete icon near the selected path.
arrowbooleanfalseAdd an arrowhead marker to the end of the connector.
lineStyle’solid’|‘dashed’|‘dotted''solid’Stroke style of the connector.
onConnectorCreatefunctionMetro.noopCalled on create. Receives detail object (see Events).
onConnectorUpdatefunctionMetro.noopCalled on update. Receives detail object (see Events).
onConnectorDestroyfunctionMetro.noopCalled on destroy. Receives detail object (see Events).

Data-attribute mapping (for live changes):

  • data-type — handled by changeAttribute(); updates connector type.
  • data-line-style — handled by changeAttribute(); updates line style.

Events can be handled either via options (onConnectorCreate, etc.) or by listening to Metro’s internal event system. The component fires:

EventDetail
connector-create{ element, options } — fired after structure and connection are created.
connector-update{ connection, type } — fired whenever the connector is re-rendered/updated.
connector-destroy{ id } — fired when the connector is destroyed.

Selection and delete:

  • Clicking on a connector path (.cl-line, .cl-curve) toggles selected-path class.
  • If deleteButton: true, a .connector-delete icon appears for the selected connection; clicking it removes that connection.
  • setType(type) — Change connector type: 'line'|'curve'|'zigzag'.
  • setPoints(pointA, pointB) — Change endpoints and re-bind auto-updates.
  • setLineStyle(style) — Set line style: 'solid'|'dashed'|'dotted'.
  • update() — Force re-calc and redraw of the connector.
  • changeAttribute(attr, newValue) — Reacts to data-type and data-line-style.
  • destroy() — Remove the connector, delete button, observers; may remove temporary points.

Static helper:

  • Metro.connector.create(pointA, pointB, options) — Programmatically create a connector instance on a dummy element appended to options.container (default body). Returns the plugin instance.

The connector uses the same variables as Linked Block for line appearance:

VariableDefault (Light)Dark ModeDescription
--linked-block-line-width1px1pxStroke width for connector shapes.
--linked-block-line-color#b1b1b1#656a70Stroke color for connector shapes and delete icon.

You can also style via the .cl-line / .cl-curve classes (stroke, stroke-width) and .selected-path state.

  • .connection-area — Shared SVG container created inside the provided container.
  • .cl-line — SVG <line> for 'line' type connectors.
  • .cl-curve — SVG <path> for 'curve' and 'zigzag' connectors.
  • .selected-path — Applied when a connector is selected (click); can be styled for focus.
  • .connector-delete — Delete button group appended to the SVG; visible when the path is selected and deleteButton is true.
  • Interactive connections: Hover a block to reveal ”+” buttons; click a side button to start connecting, then click a side button on another block to complete the connection. Temporary connector follows the cursor during the operation.
  • Active block & resize hotkey: Clicking a block toggles .active-block. If resizeHotkey is set, pressing it toggles [data-can-resize] on the active block to show/hide resizers.
  • Connector types: Supported types are line, curve, and zigzag. Default is curve.
  • The connect() API returns an object with the created connector and points, plus a generated id for later disconnect() calls.
  • Ensure all blocks are placed within a common positioned container to keep connector SVGs aligned.
  • Add only the needed number of points per side to keep diagrams readable.
  • Use CSS variables on a wrapping container to theme multiple blocks consistently.
  • Avoid modifying the internal structure (sides/points) manually after initialization—use the API methods.
  • When enabling resizable, consider minWidth, minHeight, maxWidth, and maxHeight to guide user resizing.