Getting Started
By the end of this page you'll have a projection working on your page: a remote document rendering inside a host element, with CSS isolation, full interactivity, and no iframe rectangle. If you haven't read What is Virtual Frame?, skim that first — the mental model (the remote runs in a hidden iframe; Virtual Frame mirrors its DOM into your host) makes the rest easier to follow.
There are three ways to integrate: the custom element (HTML / vanilla JS), the VirtualFrame class (when you need imperative control), and a framework wrapper (React, Vue, Svelte, Solid, Angular, plus the meta-framework variants). Pick the one that matches how your page is built and skip the rest.
Installation
Install the core package from npm:
npm install virtual-framepnpm add virtual-frameyarn add virtual-frameThe core package is everything you need for the custom element and the VirtualFrame class. Framework wrappers are separate packages — install them alongside the core (see below).
Your first projection
Path A: the <virtual-frame> custom element
If your page is plain HTML, a static site, or server-rendered templates, this is the shortest path. Load the element once, then drop <virtual-frame> anywhere it makes sense:
<script type="module">
import "virtual-frame/element";
</script>
<virtual-frame
src="./dashboard.html"
isolate="open"
style="width: 100%; height: 400px"
></virtual-frame>That's a full working projection. When the element connects to the DOM it creates a hidden iframe pointed at src, attaches a Shadow DOM to the element (because of isolate="open"), and starts mirroring the source's <body> into the shadow — with CSS rewritten so the source renders to the element's box rather than the browser viewport. User interactions are proxied back to the source iframe. When the element is removed, everything is torn down.
Size it with CSS. The projection fills the element's box, so give it a width and a height (via explicit style, a class, or flex/grid layout). An element with no size renders nothing.
See Vanilla JS for the full attribute reference and the VirtualFrame class if you need imperative control.
Path B: the VirtualFrame class
Reach for the class when you need to create, move, or destroy projections programmatically — for animations, custom lifecycles, or framework wrappers:
import { VirtualFrame } from "virtual-frame";
const iframe = document.getElementById("my-source"); // <iframe>
const host = document.getElementById("projection-host"); // <div>
const vf = new VirtualFrame(iframe, host, {
isolate: "open",
selector: "#main-content",
});
// Later, when you're done:
vf.destroy();You bring the iframe and the host element; Virtual Frame wires them together. Projection starts synchronously from the constructor, but the first content arrives once the iframe finishes loading — wait for iframe.onload or poll vf.isInitialized if you need to know when to read the projected DOM. Full lifecycle and options in the API reference.
Path C: a framework wrapper
If your page is built in a framework, use the corresponding wrapper — they handle mount/unmount, prop reactivity, refs, and state bridging idiomatically:
npm install virtual-frame @virtual-frame/reactnpm install virtual-frame @virtual-frame/vuenpm install virtual-frame @virtual-frame/sveltenpm install virtual-frame @virtual-frame/solidnpm install virtual-frame @virtual-frame/angularThen import the component / directive from the wrapper and use it like any native component. Each framework guide walks through props, lifecycle, imperative handles, and testing:
Using a meta-framework?
If you're on Next.js, Nuxt, SvelteKit, TanStack Start, SolidStart, Analog, React Router, or @lazarv/react-server, install the matching integration package instead of the client-only one. The meta-framework packages wire up SSR (inline the projection on the server, resume on the client), routing-aware navigation, and the proxy option for rewriting remote-origin requests through your host:
- React-based: Next.js · React Router · TanStack Start · @lazarv/react-server
- Vue-based: Nuxt
- Svelte-based: SvelteKit
- Solid-based: SolidStart
- Angular-based: Analog
Cross-origin in 30 seconds
Same-origin projection works out of the box. For a cross-origin source, include the bridge script once in the remote document, before your framework runtime:
<!-- Inside the cross-origin source document -->
<script src="https://unpkg.com/virtual-frame/dist/bridge.js"></script>Then use Virtual Frame on the host normally — it detects the cross-origin source and negotiates with the bridge automatically. All options (isolate, selector, streamingFps) work identically across origins. See Cross-Origin for the protocol, CSP requirements, and the proxy option.
Options at a glance
These are accepted by the VirtualFrame constructor. The custom element exposes them as kebab-case HTML attributes (streaming-fps, etc.) — see the API reference for the full mapping, and the linked guides for each option's semantics in depth.
| Option | Type | Default | Description |
|---|---|---|---|
isolate | "open" | "closed" | undefined | Shadow DOM mode for CSS isolation. See Shadow DOM. |
selector | string | undefined | CSS selector — only project a matching subtree. See Selector. |
streamingFps | number | Record<string, number> | undefined | Canvas/video streaming rate (undefined = smooth rAF, same-origin only). See Streaming FPS. |
Custom-element-only: proxy
The <virtual-frame> element also supports a proxy attribute — a same-origin prefix under which the env shim rewrites host-origin fetch/XHR back to the remote. The core VirtualFrame class doesn't accept this as a constructor option; it's a property of the env shim that the custom element and the meta-framework packages build when they create the iframe. See Cross-Origin → the env shim and the proxy option.
Sanity-checking your first projection
A few things to verify when a projection doesn't appear:
- The host element has a size. The projection fills the host's box; a zero-sized host renders zero. Set
widthandheightexplicitly (or via flex/grid layout). - The source iframe has loaded. Projection kicks off asynchronously. For the class path, wait for
iframe.onloador pollvf.isInitializedbefore asserting on the projected DOM. For the custom element and framework wrappers, usefindBy…/waitForin tests rather than synchronous queries. - For cross-origin sources, the bridge is in the remote. Open devtools on the remote (load it directly in a new tab) and check that
virtual-frame/bridgeactually executes. If you seevf:readyrepeating in the remote console, the host side never acknowledged — make sure the<virtual-frame>element is mounted and CSP isn't dropping the postMessage. - CSP /
frame-src. If your host page has a Content Security Policy,frame-srcmust allow the remote origin, andscript-srcmust allow the bridge URL if it's on a CDN. isolatefor CSS-heavy sources. Projecting a full-page app withoutisolatemixes its body/html rules with yours and usually produces a mess. When in doubt, useisolate="open".
What's next
- Framework binding. React · Vue · Svelte · Solid · Angular. Pick yours — each guide covers props, lifecycle, imperative control, testing, and framework-specific gotchas.
- Core concepts in depth. Shadow DOM (isolation, CSS rewriting, fonts), Selector (project a subtree), Cross-Origin (bridge, proxy, protocol), Streaming FPS (canvas/video).
- SSR. Server-Side Rendering — inline the projection on the server, resume on the client without a second round-trip.
- Shared state. Store — a typed, reactive message channel between host and remote.
- Production testing. Testing — the library depends on real browser primitives (MutationObserver, Shadow DOM, postMessage, canvas capture); jsdom/happy-dom won't cut it. Use Vitest browser mode or Playwright.
- Reference. API — every option, property, method, and the full custom-element attribute mapping.