Version v3.0.0 Size 3.3 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.3 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]