HTML Live Preview: Sandboxing HTML, CSS, and JavaScript Safely
How browser-based HTML renderers work, why they use iframes, and what makes client-side sandboxing safe for running arbitrary code.
Why a Sandbox Is Needed
When a tool lets you write arbitrary HTML, CSS, and JavaScript and renders it live, the rendered code must be isolated from the host page. Without isolation, user-written JavaScript could read cookies, access the DOM of the tool itself, or make network requests on behalf of the tool's origin.
The iframe as a Sandbox
The browser's built-in isolation mechanism for this is the sandboxed iframe. By rendering the preview inside an <iframe sandbox>, browsers enforce a set of restrictions by default:
- Scripts run in a unique origin — no access to the parent page's cookies or storage
- Forms cannot be submitted
- Popups and new windows are blocked
- Top-level navigation is blocked
Minimal sandbox iframe
<iframe
sandbox="allow-scripts"
srcdoc="<html>...</html>"
title="Preview"
/>allow-scripts lets JavaScript run inside the iframe without granting it access to the parent. The key point: even with scripts allowed, the sandbox origin is opaque (null) so same-origin restrictions still apply.
srcdoc vs Blob URLs
There are two ways to inject HTML into an iframe for live preview:
srcdoc attribute
Set the srcdoc attribute directly to the HTML string. Simple and supported in all modern browsers. Best for short snippets. Large HTML strings in attributes can cause performance issues.
Blob URL
const blob = new Blob([htmlString], { type: 'text/html' });
const url = URL.createObjectURL(blob);
iframe.src = url;
// Clean up when done
URL.revokeObjectURL(url);Blob URLs are better for larger documents and give the iframe its own URL object, which helps with relative path resolution inside the rendered HTML.
Content Security Policy (CSP)
For tools that want stricter control, a Content Security Policy can be applied to the iframe's content using the csp attribute or a <meta http-equiv="Content-Security-Policy"> tag injected into the rendered HTML.
<!-- Injected into the preview HTML head -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self' 'unsafe-inline';">This blocks external network requests from the preview, preventing the user's script from fetching third-party resources.
Handling CSS and JavaScript Together
A full HTML/CSS/JS renderer typically takes three separate editors — HTML, CSS, and JavaScript — and combines them into a single document before injecting it into the iframe:
function buildDocument(html, css, js) {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>${css}</style>
</head>
<body>
${html}
<script>${js}</script>
</body>
</html>`;
}Live vs Debounced Updates
Updating the iframe on every keypress is expensive — it destroys and recreates the entire document. Most preview tools use debouncing: wait until the user stops typing for 300–500 ms, then re-render. This balances responsiveness with performance.
let timer;
editor.on('change', () => {
clearTimeout(timer);
timer = setTimeout(updatePreview, 400);
});Use Cases
- Component prototyping: Quickly test a CSS animation or a web component in isolation
- Bug reproduction: Create a minimal repro for a DOM or styling issue before filing a bug report
- Email template testing: Paste HTML email templates and preview them instantly
- Learning: Experiment with HTML/CSS features without setting up a project
- Interview prep: Practice live coding exercises offline
Security Reminder
A sandbox iframe protects the host page from user code. It does not protect the user from malicious HTML they paste in — if you render untrusted content from a third party, you are running that code in your browser. Always be mindful of what you paste into any online renderer.
Try it yourself
Write HTML, CSS, and JavaScript and see a live preview instantly — all in your browser.
Open HTML Renderer →