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

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:

  • 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:

// 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 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:

// [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 allows authors to minimize specific breakpoint frames into lightweight headers.

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

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:

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:

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:

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 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 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.

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

This attribute, injected by the publisher in src/core/publisher/reset.ts, uniquely identifies which breakpoint context each DOM element belongs to. It enables canvasPointerRelay.ts to route events correctly and allows 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 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.

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 →