How Excalidraw Arrow Binding and Labeling Works: A Technical Deep Dive

Excalidraw implements arrow binding and labeling by storing FixedPointBinding objects in startBinding and endBinding fields of linear elements, while rendering labels as separate text elements attached to the arrow container's boundElements array.

The excalidraw/excalidraw repository handles complex diagram interactions through a sophisticated binding system that enables persistent connections between shapes. This article examines how the open-source whiteboard application implements Excalidraw arrow binding and labeling by analyzing the source code responsible for element attachments, focus point calculations, and text container relationships.

Arrow Binding Architecture

The FixedPointBinding Data Structure

According to the excalidraw/excalidraw source code, arrows are treated as linear elements with type: "arrow". Each arrow endpoint can bind to another element via the startBinding and endBinding fields defined in packages/element/src/types.ts (lines 24-26). These fields store FixedPointBinding objects that define the attachment geometry:

type FixedPointBinding = {
  elementId: string;          // id of the element we bind to
  fixedPoint: [number, number]; // normalized point (0‑1) on the target element
  mode?: "orbit" | "pin";    // how the arrow follows the target
};

The elementId references the target element, while fixedPoint uses normalized coordinates (0 to 1) to specify the exact attachment location on the target's bounding box. The optional mode property determines movement behavior, with "orbit" allowing the arrow to follow the target and "pin" fixing it to a specific point.

Computing Global Focus Points

When an arrow is bound, Excalidraw calculates the global focus point—the canvas coordinates where the arrow tip actually attaches. The function getGlobalFixedPointForBindableElement in packages/element/src/binding.ts (lines 2384-2420) performs this calculation by combining the target element's current geometry with the stored fixedPoint normalized values. This ensures that moving the target element automatically updates the arrow's endpoint position without modifying the underlying binding data.

Rendering Focus Indicators

The interactive canvas provides visual feedback for bound arrows through the renderFocusPointIndicator function in packages/excalidraw/renderer/interactiveScene.ts (lines 1255-1309). This renderer draws a focus-point highlight and a dashed line connecting the arrow tip to its attachment point on the target element. These visual indicators update in real-time as users drag bound elements, providing immediate feedback about the connection status.

Arrow Label Implementation

Creating Container-Bound Text Elements

Arrow labels are not properties of the arrow itself but separate text elements bound to the arrow container. The helper bindTextToContainer in packages/element/src/transform.ts (lines 18-42) creates these labels by generating a new text element and registering it in the container's boundElements array:

const bindTextToContainer = (
  container: ExcalidrawElement,
  textProps: { text: string } & MarkOptional<ElementConstructorOpts, "x" | "y">,
  scene: Scene,
) => {
  const textElement = newTextElement({
    x: 0,
    y: 0,
    textAlign: TEXT_ALIGN.CENTER,
    verticalAlign: VERTICAL_ALIGN.MIDDLE,
    ...textProps,
    containerId: container.id,
    strokeColor: textProps.strokeColor || container.strokeColor,
  });

  Object.assign(container, {
    boundElements: (container.boundElements || []).concat({
      type: "text",
      id: textElement.id,
    }),
  });

  redrawTextBoundingBox(textElement, container, scene);
  return [container, textElement] as const;
};

This approach stores the label's elementId within the arrow's boundElements array, creating a unidirectional reference from container to label.

Maintaining Horizontal Label Orientation

Because labels are regular ExcalidrawTextElement instances rather than specialized arrow properties, they remain horizontal even when the arrow rotates. The rendering system accounts for this orientation independence during the draw cycle. Additionally, the drag handling logic in dragElements.ts deliberately skips transforming the label element when moving arrows, allowing the layout system to recalculate the label's upright position based on the arrow's new geometry.

Persisting Bindings During Scene Restoration

When loading saved diagrams, the repairBinding function in packages/excalidraw/data/restore.ts (lines 36-65) reconstructs all binding relationships. This function re-links startBinding, endBinding, and container-bound labels to their respective target elements, ensuring that saved .excalidraw files fully restore connection and labeling information across sessions.

Programmatic Usage Examples

Creating an Arrow with a Label

To programmatically create a labeled arrow, use the createLabeledArrow helper available in the test utilities, which internally calls bindTextToContainer:

import { newArrowElement } from "@excalidraw/excalidraw";
import { API } from "@excalidraw/excalidraw";

/* Create a simple arrow */
const arrow = newArrowElement({
  x: 100,
  y: 100,
  width: 200,
  height: 0,
  points: [
    [0, 0],
    [200, 0],
  ],
});

/* Attach a label */
const labeledArrow = API.createLabeledArrow?.({
  arrow,
  label: {
    text: "Important flow",
  },
});

/* Add to scene */
API.updateScene({
  elements: [labeledArrow?.[0] ?? arrow],
});

The createLabeledArrow implementation resides in packages/excalidraw/tests/helpers/api.ts (lines 433-447) and demonstrates the standard pattern for container text binding.

Binding an Arrow to a Shape

To bind an existing arrow endpoint to a geometric shape programmatically:

// Assume we already have a rectangle `rect` and an arrow `arrow`
import { bindElements } from "@excalidraw/excalidraw";

bindElements({
  element: arrow,
  binding: {
    elementId: rect.id,          // bind to rectangle
    fixedPoint: [0.5, 1],       // middle of the bottom side
    mode: "orbit",              // follow rectangle when it moves
  },
  type: "end",                  // bind the arrow's end
});

This updates arrow.endBinding with a FixedPointBinding object, causing the renderer to calculate the global focus point using the logic described above.

Updating Label Content

Since labels are independent elements, modify them directly through the scene API without affecting the arrow geometry:

// `textEl` is the bound text element returned from `bindTextToContainer`
API.updateScene({
  elements: [
    {
      ...textEl,
      text: "Updated label",
    },
  ],
});

Changes to the text property automatically trigger re-rendering of the label while preserving its binding to the arrow container.

Summary

Frequently Asked Questions

What data structure does Excalidraw use for arrow bindings?

Excalidraw uses the FixedPointBinding type to define arrow bindings. This structure contains an elementId referencing the target element, a fixedPoint array with normalized coordinates (0-1) indicating the attachment location on the target's bounding box, and an optional mode property set to either "orbit" or "pin" that determines how the arrow follows target movement. These bindings are stored in the startBinding and endBinding fields of the arrow element.

How does Excalidraw keep arrow labels horizontal while allowing arrow rotation?

Arrow labels remain horizontal because they are implemented as separate text elements rather than arrow properties. The label attaches to the arrow container through the boundElements array, and the rendering system explicitly handles label positioning independently of arrow rotation. When dragging arrows, the system skips transforming the label element, allowing it to stay upright while the arrow rotates freely.

Which source files handle arrow binding calculations and rendering?

The binding logic spans several critical files: packages/element/src/types.ts defines the FixedPointBinding data structures, packages/element/src/binding.ts contains the getGlobalFixedPointForBindableElement calculation logic for attachment points, and packages/excalidraw/renderer/interactiveScene.ts implements the renderFocusPointIndicator function that draws visual connection indicators and focus highlights on the interactive canvas.

How are arrow bindings preserved when saving and loading diagrams?

During scene restoration, the repairBinding function in packages/excalidraw/data/restore.ts (lines 36-65) reconstructs all binding relationships by re-linking startBinding, endBinding, and container-bound labels to their respective elements. This ensures that saved files retain complete connection and labeling information, restoring the diagram's interactive behavior exactly as it was when saved.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →