Version v26.10.0 Size 3.5 KB gzip Dependencies Zero dependencies
Why Sandbox?
Running untrusted HTML in the main window is unsafe: arbitrary code can access the DOM, cookies, and user data. Sandbox creates an isolated <iframe sandbox="allow-scripts"> that receives content over a typed state bridge and explicit message trust boundary. The sandbox cannot reach the host page.
ts
// Before
container.innerHTML = untrustedHtml;
// After
const sandbox = createSandbox(container);
await sandbox.render(untrustedHtml);Common use cases:
- Component previews: render isolated HTML/CSS examples in documentation or design tools
- Code playgrounds: execute user-provided code with full error forwarding and state injection
- Plugin sandboxes: host third-party or user-authored plugin UI without granting host access
- User-generated content: display untrusted HTML (emails, form output, external widgets) safely
- Widget embedding: wrap third-party widgets with strict CSP and bidirectional messaging
- AI-generated UI: render LLM-produced HTML components with guaranteed isolation
| Feature | Raw <iframe> | Sandbox |
|---|---|---|
| Bundle size | 0 B (built-in) | 3.5 KB |
| Zero dependencies | ||
| Content-Security-Policy | Manual | Auto-generated, strict by default |
| Managed message protocol | Typed host state; untrusted inbound details | |
| Error forwarding | onerror + unhandledrejection → host | |
Dispose / using | Manual remove() | dispose() + [Symbol.dispose] |
Use Sandbox when you need to render untrusted or user-provided HTML in the browser with iframe isolation, CSP enforcement, and explicit host/sandbox messaging.
Consider a raw <iframe> when you only need to embed a known third-party URL: Sandbox is for programmatic srcdoc content, not URL-based embedding.
Installation
sh
pnpm add @vielzeug/sandboxsh
npm install @vielzeug/sandboxsh
yarn add @vielzeug/sandboxQuick Start
ts
import { createSandbox } from '@vielzeug/sandbox';
const container = document.getElementById('preview')!;
const sandbox = createSandbox<{ theme: 'dark' | 'light' }>(container);
sandbox.onMessage((message) => {
if (message.type === 'custom') console.log(message.event, message.detail);
if (message.type === 'error') console.error(message.message);
if (message.type === 'resize') console.log('height:', message.height);
});
try {
await sandbox.render('<ore-button variant="primary">Click me</ore-button>');
sandbox.setState({ theme: 'dark' });
} catch (error) {
console.error('Sandbox render failed', error);
} finally {
sandbox.dispose();
}Features
createSandbox<State>(): Creates an isolated<iframe sandbox="allow-scripts">with typed outbound staterender(html): Creates or replaces the document and resolves when its bridge reports readysetState(update): Pushes one or more typed state values in one messagereplaceBody(html): Updates streamed body content without resetting head scripts and stylesupdateStyle(id, css): Patches a named style without replacing the documentSandboxMessage: Keeps sandbox-controlled custom details typed asunknownon the hostSandboxBridge<State, Events>: Types authored sandbox-side state subscriptions and event emissionreadyTimeout: Rejects blocked document initialization withSandboxTimeoutError- Strict CSP: Uses
default-src 'none'and blocks network requests by default - Error forwarding: Installs before user scripts and forwards uncaught errors and rejections
- Stale-message protection: Rejects messages from superseded render generations
- Disposable: Supports
dispose(),disposalSignal, and[Symbol.dispose]