# Understanding the Architecture of Instatic's Visual Editor Canvas

> Explore Instatic's visual editor canvas architecture. Discover its layered, iframe-based design for mirrored page rendering and independent editing control. Learn how Instatic ensures a seamless editing experience.

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

---

**Instatic's visual editor canvas is built as a layered, iframe-based rendering surface that uses per-breakpoint iframes to mirror the published page while maintaining full editing control through isolated gesture handling, selection contexts, and extensible overlay systems.**

The visual editor canvas in CoreBunch/Instatic provides a WYSIWYG editing environment that balances fidelity to the final published output with sophisticated design-time interactions. Unlike simple DOM-based editors, the architecture isolates site content within iframe sandboxes while layering editor-specific controls, transforms, and interactions on top. This ensures that CSS cascades, media queries, and runtime scripts behave exactly as they would in production, while the editor maintains granular control over selection, drag-and-drop, and keyboard shortcuts.

## Three-Layer Canvas Architecture

The architecture of Instatic's visual editor canvas comprises three distinct vertical slices that handle interaction, transformation, and preview modes.

### Root and Interaction Layer

At the top level, [`src/admin/pages/site/canvas/CanvasRoot.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/CanvasRoot.tsx) initializes the canvas environment. This component creates the `CanvasViewportActionsContext` and `CanvasSelectionContext` providers, mounts the `<DndContext>` from `@dnd-kit/core` for drag-and-drop operations, and wraps the entire surface in an `ErrorBoundary` keyed to the current page. It hosts the UI chrome—including the notch toolbar ([`CanvasNotch.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasNotch.tsx)), breakpoint selector, and mode toggle—while capturing global input events like wheel gestures, pinch-to-zoom, and keyboard shortcuts through `useCanvasKeyboardShortcuts`.

### Transform and Render Layer

[`src/admin/pages/site/canvas/CanvasTransformLayer.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/CanvasTransformLayer.tsx) handles the geometric transform pipeline and iframe orchestration. This layer renders a set of `<BreakpointFrame>` iframes—one per active breakpoint (mobile, tablet, desktop)—each containing a live copy of the site's CSS via [`CanvasClassCss.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasClassCss.tsx) and the page's node tree. By mounting each breakpoint in its own iframe, the canvas preserves the exact CSS cascade and media query behavior of the published site, while the editor's `--canvas-*` design tokens remain isolated in the parent document.

### Live Surface Layer

For preview-only workflows, [`src/admin/pages/site/canvas/CanvasLiveSurface.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/CanvasLiveSurface.tsx) provides a read-only view. This single iframe displays the fully-published page without attaching pan, zoom, or edit gestures, offering a true-to-life preview toggle that designers can switch to instantly from design mode.

## Core Subsystems and Implementation Details

### Iframe-Per-Breakpoint Rendering

Every breakpoint receives its own `IframeFrameSurface` instance. CSS class injection, user stylesheets, and font tokens mount inside each iframe through [`CanvasClassCss.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasClassCss.tsx), ensuring that the editor's stacking context never pollutes the site's global CSS. This isolation allows the canvas to render mobile, tablet, and desktop views simultaneously while maintaining accurate cascade resolution.

### Selection and Hover Management

Node selection state lives in the global editor store (`selectedNodeId`, `selectedNodeIds`) rather than React component state. The `CanvasSelectionContext` defined in [`src/admin/pages/site/canvas/CanvasContexts.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/CanvasContexts.tsx) provides stable callbacks to child components, while individual `NodeRenderer` components subscribe directly to store-derived booleans. This architecture ensures that only the affected nodes re-render during click or hover interactions, maintaining performance across large documents.

### Drag-and-Drop Resolution

The canvas owns its dedicated DnD context separate from the rest of the admin interface. Drop resolution logic resides in [`src/admin/pages/site/canvas/canvasDnd.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/canvasDnd.ts), specifically `resolveCanvasDropTarget` and `resolveCanvasPointerInsertionDrop`, which are shared with the DOM panel tree. This guarantees parity between canvas drags and side-panel reordering operations, with insertion logic handling both module picker drops and media library assets through [`canvasInsertionDrop.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasInsertionDrop.ts).

### Keyboard Shortcuts and Actions

Input handling flows through `useCanvasKeyboardShortcuts`, which consults the central key-binding registry (`@admin/spotlight/keybindings`). Actions such as Delete, Duplicate, Copy, Cut, and Paste dispatch to the editor store, supporting multi-node operations batched into single undo steps via `mutateActiveTree`.

### Modals and Overlay System

Several specialized components manage floating UI elements:

- **Rename Dialog**: `CanvasRenameDialog` and `useCanvasRenameDialog` handle inline node renaming.
- **Context Menu**: `CanvasLayerContextMenu` opens on right-click, using `clientPointToEditorDoc` to translate iframe-relative coordinates to the editor document space.
- **Plugin Overlays**: `PluginCanvasOverlayLayer` renders plugin-registered components after the transform layer, ensuring overlays appear above all nodes but only in design mode.

### Design Mode Controls

The `CanvasNotch` component hosts lazy-loaded mode-specific controls. `VisualComponentModeControl` renders when editing isolated visual components, while `TemplateModeControl` activates for template-preview workflows. The mode toggle itself lives in [`src/admin/pages/site/canvas/CanvasModeToggle.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/CanvasModeToggle.tsx), switching between "Design" and "Live" canvas views.

### Error Boundaries and Resilience

The canvas implements an `ErrorBoundary` with location key "canvas" that resets whenever the page or document changes (via `resetKeys`). This prevents a single broken module or runtime error from crashing the entire editor interface.

### Runtime Script Injection

When "Run scripts" is enabled, `useRuntimeScriptBuild` compiles a single script bundle injected into all breakpoint iframes simultaneously. This avoids per-frame rebuilds and ensures consistent JavaScript execution across all viewport previews.

## Data Flow and State Management

The canvas follows a unidirectional data flow:

1. **Store hydration**: The editor store provides the active page via `selectActiveCanvasPage`, along with breakpoints, selected node IDs, and user permissions.
2. **Template composition**: [`CanvasComposition.ts`](https://github.com/CoreBunch/Instatic/blob/main/CanvasComposition.ts) computes the full template chain for the active document using `resolveEditorWrapperTemplates`.
3. **Rendering**: `CanvasTransformLayer` renders each breakpoint iframe, injecting compiled class CSS and mounting either `CanvasLiveSurface` or the interactive tree renderer.
4. **Mutation**: Interaction events invoke store actions (`selectNode`, `deleteNode`, `wrapNode`) which mutate the immutable page tree through `mutateActiveTree`, triggering targeted re-renders of only affected nodes.

## Practical Implementation Examples

Developers can extend the canvas through its plugin overlay API or manipulate selection programmatically.

Registering a custom canvas overlay:

```tsx
// Example: Adding a custom overlay to the canvas
import { useCanvasOverlay } from '@site/hooks/useCanvasOverlay';

function MyOverlay() {
  const { canvasRootRef } = useCanvasOverlay(); // gets the root div ref
  return (
    <div
      style={{
        position: 'absolute',
        top: 10,
        left: 10,
        background: 'rgba(0,0,0,0.2)',
        padding: '4px 8px',
        borderRadius: 4,
      }}
    >
      Custom overlay
    </div>
  );
}

// Register it via a plugin (plugin‑canvas‑overlay)
export const register = (api) => {
  api.registerCanvasOverlay(() => <MyOverlay />);
};

```

Programmatic node selection:

```tsx
// Example: Selecting a node programmatically (e.g. from a sidebar)
import { useEditorStore } from '@site/store/store';

function selectFirstHeader() {
  const page = useEditorStore.getState().selectedPage;
  const headerNodeId = Object.values(page.nodes).find(
    (n) => n.moduleId === 'base.heading'
  )?.id;
  if (headerNodeId) useEditorStore.getState().selectNode(headerNodeId, 'replace');
}

```

## Summary

- Instatic's canvas uses a **three-layer architecture**: Root/Interaction, Transform/Render, and Live Surface, implemented in [`CanvasRoot.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasRoot.tsx), [`CanvasTransformLayer.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasTransformLayer.tsx), and [`CanvasLiveSurface.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasLiveSurface.tsx).
- **Per-breakpoint iframes** isolate CSS cascades while maintaining accurate media query rendering across viewports.
- **Global store subscription** minimizes re-renders during selection and hover, with individual `NodeRenderer` components connecting directly to state.
- **Shared DnD resolution** in [`canvasDnd.ts`](https://github.com/CoreBunch/Instatic/blob/main/canvasDnd.ts) ensures consistent drag-and-drop behavior between the canvas and DOM panel.
- **Plugin overlay system** via `PluginCanvasOverlayLayer` allows third-party extensions to render above the canvas content in design mode.
- **Error boundaries and runtime script injection** provide resilience and accurate JavaScript execution across all breakpoint previews.

## Frequently Asked Questions

### How does Instatic's canvas handle CSS isolation between the editor and the site content?

The canvas renders each breakpoint in its own iframe via [`CanvasTransformLayer.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasTransformLayer.tsx), injecting site-specific CSS through [`CanvasClassCss.tsx`](https://github.com/CoreBunch/Instatic/blob/main/CanvasClassCss.tsx). This keeps the site's global styles scoped within the iframe while the editor's `--canvas-*` tokens and UI chrome remain in the parent document, preventing cascade pollution.

### What drives the selection system in the Instatic canvas?

Selection state lives in the central editor store (`selectedNodeId`, `selectedNodeIds`), not in React local state. The `CanvasSelectionContext` provides callback stability, while individual node components subscribe to boolean selectors from the store, ensuring that only the clicked or hovered nodes re-render rather than the entire tree.

### Can plugins add visual elements to the Instatic canvas?

Yes. Plugins can register overlay components through the `PluginCanvasOverlayLayer` system. These overlays render after the transform layer in design mode, appearing above the page content but below modal dialogs, and receive the canvas root ref via `useCanvasOverlay` for positioning.

### How does the canvas maintain undo/redo parity between the visual canvas and the DOM panel?

Both interfaces share the same drop resolution logic exported from [`src/admin/pages/site/canvas/canvasDnd.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/canvas/canvasDnd.ts), specifically `resolveCanvasDropTarget` and `resolveCanvasPointerInsertionDrop`. Store actions like `mutateActiveTree` batch mutations into single undo steps regardless of whether the interaction originated from the canvas or the side panel.