react-frame-component
A React component that renders your app or a subtree inside a real iframe, exposing the frame's own window and document via context.
Repository Health
Technical Analysis
react-frame-component wraps any React tree in a genuine <iframe>, mounting it via ReactDOM.createPortal so the isolated document still participates in the same React tree — state, refs, and event handling continue to work exactly as they would outside the frame. It solves a narrow but recurring problem: encapsulating a component’s CSS and DOM from the surrounding page (email/newsletter previews, widget/theme sandboxes, style-isolated demos) without giving up React’s component model.
Consumers reach the iframe’s own window and document through a FrameContext (via useFrame() or FrameContextConsumer), letting nested components register frame-scoped event listeners, inject stylesheets through the head prop, or measure/focus the iframe DOM directly. contentDidMount/contentDidUpdate callbacks mirror React’s own lifecycle so consumers can synchronize frame-internal effects with the outer render cycle.
What You Get
- A drop-in
<Frame>component that behaves like a normal React element (accepts children, style, ref, and any native iframe attribute) - Portal-based rendering into the iframe’s document via
ReactDOM.createPortal, so state and context continue to flow normally across the frame boundary FrameContext/useFrame()for accessing the iframe’s ownwindowanddocumentfrom descendant components- A
headprop for injecting stylesheets or other elements into the iframe’s<head>, plusmountTarget/initialContentfor controlling where and how content mounts inside a custom frame document - TypeScript typings shipped via a hand-authored
index.d.ts, plus dual ESM/UMD builds for bundler and script-tag consumption
Common Use Cases
- Rendering user-generated or third-party HTML/CSS in a sandboxed preview (email templates, theme editors, style-isolated widgets) without leaking styles into the host page
- Building WYSIWYG or live-preview editors where the edited content must render with its own independent stylesheet and DOM
- Embedding print-preview or PDF-style layouts that need exact CSS isolation from the surrounding app
- Creating design-system or component demo pages where each example needs a clean, isolated document to avoid CSS bleed between examples
Under The Hood
Architecture
The library is a small, single-purpose module (Frame.jsx, Content.jsx, Context.jsx, index.js) with no internal layering: Frame is a class component that holds a ref to the raw <iframe> element, waits for the frame’s document to become ready (DOMContentLoaded, with a polling-interval fallback for cold-cache cases where that event never fires), then uses ReactDOM.createPortal twice — once to inject head content into the frame’s <head>, once to mount a Content wrapper (which fires contentDidMount/contentDidUpdate) wrapped in a FrameContextProvider into the frame’s mount target. Because rendering is gated entirely on state.iframeLoaded and a defensive getDoc()/getMountTarget() null-check, the frame-readiness detection is the one piece everything else depends on — a regression there breaks head injection, context propagation, and content mounting all at once, as a recent changelog entry (a race-condition null-pointer fix for issue #250) illustrates.
Tech Stack
React is a peer dependency spanning 16.8 through 19, with prop-types for runtime type declarations and a hand-authored index.d.ts for TypeScript consumers. The library builds with Vite into dual ESM and UMD bundles, externalizing react, react-dom, react/jsx-runtime, and prop-types so consumers supply their own copies. Testing runs on Vitest plus @testing-library/react and @playwright/test’s browser mode (real Chromium/Firefox iframe behavior rather than a jsdom approximation). Releases are versioned and changelogged via Changesets, and CI (GitHub Actions) runs lint, browser-based tests, and a production build on every push and pull request.
Code Quality
The test suite is proportionally large for the codebase’s size — a 15KB Frame.spec.jsx alone covers iframe creation, prop passthrough, head injection, custom mount targets, dangerouslyUseDocWrite, and ref forwarding, run against real browser engines via Playwright rather than mocked DOM. Error handling favors defensive null checks over thrown exceptions (getDoc()/getMountTarget() return null rather than throwing when the frame document isn’t ready), and the changelog shows this being actively hardened in response to a filed bug rather than left alone. ESLint (flat config, React + React Hooks recommended rulesets) and Prettier enforce consistent style, and all three CI jobs (lint, test, build) gate merges to master.
API Design
The public surface is deliberately tiny: a single <Frame> component plus a FrameContext/useFrame() escape hatch, built entirely on familiar React idioms (children, style, ref, lifecycle-shaped callbacks) rather than a bespoke API. Getting started requires nothing more than wrapping existing JSX in <Frame> — no provider setup, no configuration object — while head, initialContent, and mountTarget progressively expose the frame’s more advanced document-control knobs only when needed. Documentation lives entirely in the README’s props table with no separate docs site, which is proportionate to how small and stable the API surface actually is.
Used by 2 apps in this directory
Chaskiq
CRM · Customer Support
Self-hosted live chat, video calls, help center, and marketing automation — a full-stack Intercom alternative you run on your own infrastructure.
Notesnook
Note Taking · File Storage · Security
End-to-end encrypted, open-source note-taking where your data stays yours — even from the server.