build69
build69  /  Components  /  Layout & Surfaces  /  EditorHost

EditorHost

v1.0.2Updated 19 Oct 2026

The frame around a full-screen editor somebody else wrote — ONLYOFFICE, Collabora, a third-party designer. Thin on purpose: the part every host rewrites is not the iframe but the three states around it. LOADING is a cover OVER the frame, not instead of it, because the editor inside needs the frame present and sized to lay itself out. FAILED comes from a timeout, because a cross-origin iframe fires no error event for a 404, a 500, an X-Frame-Options refusal or a dead host — load simply never comes — and Retry remounts the frame with a fresh key rather than resetting a flag. OBSCURED makes the frame inert so a dialog above it keeps its drag and its focus. It never claims to know whether the editor INSIDE is ready; same-origin policy means it cannot, and onMessage is the seam for the editor's own protocol.

Preview

Live · 6 stories
DefaultOpen ↗
▸Why this is recommendediframeembededitoronlyoffice+5

The default, out-of-the-box EditorHost.

Recommended for+14
  • ✓Embed a third-party editor full screen
  • ✓Host an iframe nobody here controls, with loading and error states
  • ✓Show a document editor inside the app shell
  • ✓Put a dialog over an embedded editor without losing the click
Tags+6
iframeembededitoronlyofficethird-partyhostframesandboxdefault
Features+4
  • Loading cover over a mounted, sized frame — mounting the frame late gives an editor that boots into a zero-height box
  • Timeout-based failure with Retry, because a cross-origin frame reports nothing for the common failures
  • Retry remounts the iframe (fresh key), since some browsers will not re-request a URL they have just failed on
  • obscured → inert, which covers the pointer AND the tab order in one word, for a dialog layered over the editor
  • A documented default sandbox that withholds allow-top-navigation and allow-downloads; sandbox="" and sandbox={false} both mean what HTML says they mean
  • Required title — an iframe with no accessible name is an unlabelled landmark a reader cannot tell from any other frame
  • onMessage filtered to THIS frame, so a host does not re-filter every other embed and library on the page
  • A new src is a new wait: back to loading, frame remounted
  • Toolbar slot for the host’s own chrome above the frame
  • i18n: labels prop over DEFAULT_EDITOR_HOST_LABELS — the loading line, the failure title and hint, Retry and the region name
Not recommended for−2
  • ✕Showing a document the app fetched itself (use DocumentViewer, which owns the fetch, the blob and the retry)
  • ✕Comparing two documents (use DocumentCompare)
  • ✕A small inline preview (use Card with an image or DocumentViewer)
  • ✕Rendering untrusted HTML the app composed (use Markdown, which sanitises)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "EditorHost" })

Usage

import { EditorHost } from '@/components/surfaces/editor-host';

<EditorHost
  src={editorUrl}
  title={doc.name}
  loadTimeoutMs={20000}
  obscured={confirmOpen}
  onMessage={(e) => { if (e.data?.type === 'saved') markClean(); }}
  toolbar={<><BackButton /><DocName /><SaveButton /></>}
/>

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
src

The editor's URL. Changing it remounts the frame and returns to loading

stringyes
title

What the editor is. Names the iframe AND the role="application" region; required because an unnamed frame is an unnamed landmark

stringyes
sandbox

The iframe sandbox. Defaults to DEFAULT_EDITOR_SANDBOX ("allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals") — deliberately WITHOUT allow-top-navigation, so the embedded page cannot navigate the host away, and without allow-downloads. Note that allow-scripts with allow-same-origin lets a SAME-ORIGIN frame remove its own sandbox, so the default is a real boundary only for cross-origin content; drop allow-same-origin for untrusted same-origin pages. “false” removes the attribute entirely; an empty string is the most restrictive frame there is

string | false—
allow

The iframe allow policy, e.g. "clipboard-write; fullscreen". Omitted entirely when not given

string—
loadTimeoutMs

How long to wait for the frame’s load before calling it failed (default 20000). This is the ONLY failure signal a cross-origin frame gives: a 404, a 500, an X-Frame-Options refusal and a dead host all fire nothing. 0 waits forever

number—
loading

Forwarded to the iframe (default "lazy" for an editor that may be offscreen)

'eager' | 'lazy'—
onLoad

Fires when the frame's DOCUMENT loads. Not “the editor is ready” — the component cannot know that and does not pretend to

() => void—
onError

Fires on the browser’s own error event or on the deadline, saying which

(reason: 'error' | 'timeout') => void—
onMessage

Every window message whose source is THIS frame — the editor's own postMessage protocol, and the only honest channel to what is happening inside

(event: MessageEvent) => void—
obscured

Something is layered over the editor. The frame goes inert, leaving the pointer and the tab order, so a dialog above keeps its drag and its focus

boolean—
toolbar

The host's own chrome above the frame — a back button, the document name, a Save

ReactNode—
errorState

Replaces the built-in failed state entirely, Retry included

ReactNode—
labels

Override any built-in string. Keys: loading, errorTitle, errorTimeoutHint, errorHint, retry, editorRegion ({title}). English defaults ship verbatim as DEFAULT_EDITOR_HOST_LABELS

Partial<EditorHostLabels>—

Related