Overlays are the deepest, rarest extensibility surface. Most BuilderJS projects ship without ever authoring one. Read this only if your custom Element needs **on-canvas affordances** the engine's stock overlays don't cover — resize handles for a custom dimensional element, a crop UI for a custom image-like element, drag handles, info popovers anchored to specific corners.

## What an Overlay IS

An Overlay is **per-element on-canvas chrome**: visible UI affordances that float over (or alongside) the rendered element when it's selected. The shipped engine ships overlays for `ImageElement` (crop / upload / corner-radius), `GridElement` (column-resize handles), and the universal selection-frame (the bounding rectangle around the active element).

Where Controls modify settings via the right-rail panel, Overlays modify settings **directly on the canvas** — drag a corner of an image to crop, drag a column edge to resize, drag the selection frame's bottom-right handle to scale.

## When to author one

Most domain-specific elements **don't need an overlay**. Three signals you do:

1. The buyer's mental model is "I edit this on the canvas, not in the panel" — e.g. a Map element where pan / zoom should be on-canvas.
2. The element has a dimensional property the buyer wants to tweak by feel — e.g. an embed's iframe height, a chart's bar count.
3. The element has a rare interaction not covered by any shipped Control — e.g. a polygon vertex editor, a colour-stop gradient editor.

If your element doesn't match these signals, ship it with `getControls()` only and skip this doc. Most buyers do.

## The contract

`getOverlays()` is the **sibling of `getControls()`** — same calling convention, different purpose:

```javascript
class MyElement extends BaseElement {
    /* …getControls() returns Control instances for the right rail… */

    getOverlays() {
        return [
            new ResizeHandleOverlay(this),
            new InfoPopoverOverlay(this, { anchor: 'top-right' }),
        ];
    }
}
```

Each Overlay subclasses `BaseOverlay` (or one of the shipped overlays). The engine pipes the active element through every Overlay's render hook when selection changes; the Overlay paints its chrome onto the canvas above the element.

## The reference reading

Authoring an Overlay isn't a 1500-word walkthrough — it's a careful read of the canonical spec plus the shipped Overlays as reference. Two stops:

1. **The canonical spec** — `docs/core/OVERLAY.md`. 2900 LOC. Read §3 (BaseOverlay class spec) + §6 (authoring tutorial). The rest is engine-internal architecture; you don't need it as a buyer.

2. **The shipped Overlays** — under `src/includes/<Name>Element.js` look for `getOverlays()` overrides. The richest example is `ImageElement` (crop overlay + upload overlay + corner-radius overlay). `GridElement` has the column-resize overlay. Each Overlay class is in its own file (`ImageCropOverlay.js`, `GridResizeOverlay.js`, …) — read it line by line.

## A starter shape

For when you've decided to author one. The legal-minimum subclass:

```javascript
import BaseOverlay from './BaseOverlay.js';

class MyElementOverlay extends BaseOverlay {
    constructor(element) {
        super(element);
    }

    render() {
        // Build your chrome DOM here; append to this.containerEl.
        // The engine has already positioned `this.containerEl` over
        // the active element's bounding rect. Your job is to fill it.
        const handle = document.createElement('button');
        handle.className = 'my-overlay-handle';
        handle.textContent = '↔';
        handle.addEventListener('mousedown', (event) => this._beginDrag(event));
        this.containerEl.appendChild(handle);
    }

    _beginDrag(event) {
        // Pointer-tracking. Mutate the element's state on each move,
        // call this.element.notifySyncListeners() to repaint canvas.
    }
}
```

The engine handles bounding-rect tracking + selection state for you; the Overlay only owns its own chrome + the interactions it surfaces.

## Why this doc is short

Authoring an Overlay is **engine-source-deep work**. The pattern doesn't compress to a 30-minute walkthrough — every overlay you author is closely coupled to the element's geometry + the canvas's coordinate space. Going deeper here would either be (a) repeating `OVERLAY.md` (which is already canonical) or (b) papering over the real complexity.

If you're committed to authoring an Overlay, allocate a half-day, read the source carefully, and copy a shipped Overlay as your starting point.

## Did you make it?

If you're past this page **without authoring an Overlay**, you've made the right call. Most buyer projects ship the engine plus one or two custom Elements + Controls and never touch this surface.

If you DO need to author one:

- ✅ Read `docs/core/OVERLAY.md` §3 + §6 in full.
- ✅ Identify the shipped Overlay closest to your need (`ImageCropOverlay`, `GridResizeOverlay`, `BlockDragHandleOverlay`).
- ✅ Copy that file → rename → tweak. Don't author from scratch.
- ✅ Test: select your element, see the Overlay paint, interact with it, watch `notifySyncListeners()` repaint the canvas.

What's next?

- → [Wire a real backend](/docs/13-backend/) — the bigger leap: production save / load / auth / multi-tenant.
- 🔗 `docs/core/OVERLAY.md` — the canonical spec.
- 🔗 `src/includes/ImageElement.js` — `getOverlays()` returning crop + upload + corner-radius overlays.

Stuck?

- 🐛 file an issue with: your element class, the Overlay subclass attempt, the canvas-coordinate behaviour you expected vs got.
- 🐛 the Overlay layer is one of the densest parts of the engine; expect a learning curve. The shipped overlays are battle-tested across Phase-3 — they're your reference, not just inspiration.
