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.

Library
npm
v5.3.2
1,812stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
52/100Fair
Development Activity24
Maintenance20
Community64
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture80
Code Quality78
Innovation80
Learning Curve55

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 own window and document from descendant components
  • A head prop for injecting stylesheets or other elements into the iframe’s <head>, plus mountTarget/initialContent for 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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search