# How Instatic's Canvas Editor Implements Multi-Breakpoint Frames: Side-by-Side Iframe Architecture

> Explore Instatic's multi-breakpoint frames architecture. Learn how it uses side-by-side iframes and a shared React node tree for real-time design editing.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-27

---

**Instatic renders a dedicated iframe for each breakpoint in design mode, sharing a single React node tree across all frames while applying synchronized zoom and pan transforms to enable real-time multi-breakpoint editing.**

Instatic is an open-source visual site builder that empowers designers to edit responsive layouts across multiple device widths simultaneously. The **Instatic multi-breakpoint canvas editor** achieves this seamless editing experience by instantiating isolated iframes for every defined breakpoint, then coordinating their state through a unified store that manages view modes, collapsed frames, and runtime script injection.

## Design Mode vs. Live View: Two Canvas Architectures

The canvas editor operates in two distinct viewing modes defined in [`src/admin/pages/site/store/slices/canvasSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/store/slices/canvasSlice.ts):

- **Design Mode** (`canvasView === 'design'`) – Renders every breakpoint as a separate iframe positioned side-by-side on an infinite canvas
- **Live Mode** (`canvasView === 'live'`) – Displays only the **active breakpoint** frame at 100% width for focused editing

When switching between these modes via `setCanvasView()`, the editor reuses the same underlying iframe instances but rearranges their layout容器 to match the selected view mode.

## Canvas State Management in canvasSlice.ts

### View Modes and Breakpoint Tracking

The canvas store slice defines the `CanvasView` type that controls the multi-frame rendering logic:

```typescript
// src/admin/pages/site/store/slices/canvasSlice.ts (lines 20-31)
type CanvasView = 'design' | 'live'

```

The slice maintains critical state for multi-breakpoint coordination:

- **`activeBreakpointId`** – Tracks which breakpoint currently receives user interactions
- **`collapsedBreakpointIds`** – Array of breakpoint IDs currently minimized to headers (improves performance)
- **`zoom`**, **`panX`**, **`panY`** – Global transform values applied uniformly to all breakpoint frames

### Synchronized Viewport Navigation

Pan and zoom gestures captured by [`src/admin/pages/site/canvas/canvasPanInput.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/canvasPanInput.ts) update the global transform state. These values are applied identically to **every breakpoint iframe**, preserving spatial relationships across frames while maintaining the illusion of a single infinite canvas workspace.

## Rendering Breakpoint Frames with Shared State

### The data-breakpoint-id Attribute

To distinguish between frames sharing the same document structure, Instatic injects a `data-breakpoint-id` attribute onto the root element of each rendered tree. As noted in [`src/core/publisher/reset.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/reset.ts):

```typescript
// [data-breakpoint-id] prefix so the canvas could share a document with
// canvas now renders each breakpoint frame inside its own iframe

```

This attribute allows the canvas overlay system to route events and compute geometry specific to each breakpoint frame while maintaining a single source of truth for the node tree.

### Side-by-Side Iframe Implementation

In design mode, the editor creates **one iframe per breakpoint** and mounts the identical React node tree into each. Because all frames reference the same underlying state:

- **Selection synchronization** – Clicking a node in one frame highlights the corresponding element in all other frames
- **Property editing** – Changing styles or content updates immediately across all breakpoints
- **Structural changes** – Adding or removing DOM nodes reflects instantly in every frame

Each iframe renders at its native breakpoint width, constrained within the canvas container that applies the global zoom and pan transforms.

## Performance Optimizations for Multi-Frame Editing

### Collapsing Breakpoint Frames

Sites with numerous breakpoints can strain browser resources when rendering multiple iframes simultaneously. The `collapsedBreakpointIds` array in [`canvasSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasSlice.ts) allows authors to minimize specific breakpoint frames into lightweight headers.

Toggle a frame's collapsed state using the store action:

```tsx
import { useEditorStore } from '@site/store'

const toggle = useEditorStore((s) => s.toggleBreakpointCollapsed)

<button onClick={() => toggle('mobile')}>
  Toggle Mobile Frame
</button>

```

Collapsed frames are removed from the DOM and replaced with slim header bars, then re-instantiated on demand when expanded.

### Runtime Script Injection

Interactive behavior requires executing the site's JavaScript inside the sandboxed iframes. The boolean `runScripts` flag in the canvas slice controls whether runtime scripts are injected into breakpoint frames:

```tsx
import { useEditorStore } from '@site/store'

const setRun = useEditorStore((s) => s.setRunScripts)

<button onClick={() => setRun(true)}>
  Run Site Scripts
</button>

```

Enabling scripts allows authors to test hover states, click handlers, and dynamic content while maintaining the multi-breakpoint view.

## Switching Between Canvas Views

Implement view mode toggling in your custom UI components:

```tsx
import { useEditorStore } from '@site/store'

const setView = useEditorStore((s) => s.setCanvasView)

<button onClick={() => setView('design')}>
  Design (all breakpoints)
</button>

<button onClick={() => setView('live')}>
  Live (single breakpoint)
</button>

```

## Key Implementation Files

The multi-breakpoint canvas relies on these core modules:

- **[`src/admin/pages/site/store/slices/canvasSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/store/slices/canvasSlice.ts)** – Central state for view modes, zoom/pan coordinates, active breakpoint tracking, and frame collapse management
- **[`src/core/publisher/reset.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/reset.ts)** – Injects `data-breakpoint-id` attributes to enable frame-specific DOM identification
- **[`src/admin/pages/site/canvas/canvasPanInput.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/canvasPanInput.ts)** – Handles gesture input and applies uniform transforms to all breakpoint frames
- **[`src/admin/pages/site/canvas/canvasOverlayGeometry.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/canvasOverlayGeometry.ts)** – Calculates positioning for UI overlays that align with specific breakpoint iframes
- **[`src/admin/pages/site/canvas/canvasPointerRelay.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/canvasPointerRelay.ts)** – Routes pointer events from the shared canvas surface to the correct breakpoint iframe based on `data-breakpoint-id`
- **[`src/admin/pages/site/canvas/canvasTreeLadder.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/canvasTreeLadder.ts)** – Constructs the inspection tree UI while respecting the active breakpoint selection

## Summary

- **Instatic's canvas editor** creates one iframe per breakpoint in design mode, displaying them side-by-side on a synchronized canvas
- The **[`canvasSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasSlice.ts)** store manages global zoom, pan, view modes, and frame collapse states that apply to all breakpoints simultaneously
- **`data-breakpoint-id`** attributes in [`src/core/publisher/reset.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/reset.ts) enable the canvas to share a single React node tree across multiple iframe render targets
- **Collapsing frames** and **`runScripts`** toggles optimize performance and functionality when working with complex, interactive sites across many breakpoints

## Frequently Asked Questions

### How does Instatic keep breakpoint frames synchronized?

All breakpoint iframes mount the **same React node tree**, so state changes propagate automatically to every frame. The canvas store coordinates global transforms (zoom/pan) and selection state across frames using the `activeBreakpointId` and uniform transform application via [`canvasPanInput.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasPanInput.ts).

### What is the purpose of the data-breakpoint-id attribute?

This attribute, injected by the publisher in [`src/core/publisher/reset.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/reset.ts), uniquely identifies which breakpoint context each DOM element belongs to. It enables [`canvasPointerRelay.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasPointerRelay.ts) to route events correctly and allows [`canvasOverlayGeometry.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasOverlayGeometry.ts) to calculate UI positioning relative to specific breakpoint frames.

### How does Instatic handle performance with many breakpoints?

The **`collapsedBreakpointIds`** array in [`canvasSlice.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasSlice.ts) tracks which frames should render as lightweight headers rather than full iframes. Authors can toggle specific breakpoints closed using `toggleBreakpointCollapsed()`, removing heavy DOM instances until needed again.

### Can you run JavaScript inside the canvas iframes?

Yes. The **`runScripts`** boolean flag in the canvas slice controls script injection. When enabled, the editor injects the site's runtime scripts into each breakpoint iframe, allowing authors to test interactive behaviors while maintaining the multi-breakpoint editing view.