zudo-doc
GitHub repository

Type to search...

to open search from anywhere

HtmlPreview

Created Mar 16, 2026Updated Jun 11, 2026Takeshi Takatsudo

Interactive HTML/CSS preview with viewport switching and collapsible source code display.

<HtmlPreview> renders live HTML/CSS demos inside an isolated iframe with viewport presets (Mobile / Tablet / Full) and a collapsible source code panel with syntax highlighting. It is globally available in all MDX files without imports.

Basic Usage

Basic Box
<HtmlPreview
  title="Basic Box"
  html={`
    <div class="box">Hello, world!</div>
  `}
  css={`
    .box {
      padding: 24px;
      background: #3b82f6;
      color: #fff;
      border-radius: 8px;
      font-family: system-ui, sans-serif;
      text-align: center;
    }
  `}
/>

Responsive Layout

Use the viewport buttons (Mobile / Tablet / Full) to see how content reflows at different widths. Try resizing with the drag handle at the bottom-right of the preview area.

Responsive Grid

HTML Only

When no css prop is provided, only the HTML source is shown.

HTML Only

Default Open Code

Use defaultOpen to show the source code expanded by default.

Code Visible by Default
HTML
<button class="btn">Click me</button>
CSS
.btn {
  padding: 10px 20px;
  background: #8b5cf6;
  color: #fff;
  border: none;
  border-radius: 6px;
  font-size: 14px;
  font-family: system-ui, sans-serif;
  cursor: pointer;
}
.btn:hover {
  background: #7c3aed;
}

Fixed Height

Use the height prop to set a fixed iframe height instead of auto-sizing.

Fixed Height (300px)

External Resources

You can inject external resources (CSS frameworks, webfonts, scripts) into previews. Configure globally via settings.ts or per-component via props.

Global Configuration

Set htmlPreview in src/config/settings.ts to apply resources to all previews:

htmlPreview: {
  head: `<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap">`,
  css: `body { font-family: 'Noto Sans JP', sans-serif; }`,
  js: `console.log('preview loaded');`,
}

Per-Component Props

Use head and js props to add resources to individual previews. These are merged after global values.

With JS
<HtmlPreview
  title="With JS"
  html={`
    <div id="output">Waiting...</div>
  `}
  css={`
    #output {
      padding: 16px;
      font-family: system-ui, sans-serif;
      color: #334155;
    }
  `}
  js={`
    document.getElementById('output').textContent = 'Hello from JS!';
  `}
/>

Security & the sandbox prop

Previews render inside an isolated <iframe srcdoc>. Its sandbox attribute defaults to allow-scripts allow-same-origin when the preview contains scripts (a js prop or a <script> in head), and allow-same-origin otherwise.

:::danger[Trust assumption] allow-scripts + allow-same-origin together void the iframe sandbox — scripts inside the preview share the parent page's origin and can reach the parent document. zudo-doc keeps this default because preview content is author-trusted MDX, and allow-same-origin is what powers the auto-height measurement.

If your project renders semi-trusted or user-submitted HTML, override it with a stricter value via the sandbox prop. :::

<!-- Maximally restrictive: no script execution, opaque origin -->
<HtmlPreview html={untrusted} sandbox="" height={400} />

<!-- Allow scripts but keep an opaque origin (script can't reach the parent) -->
<HtmlPreview html={untrusted} sandbox="allow-scripts" height={400} />

Removing allow-same-origin gives the iframe an opaque origin, which blocks the parent from reading iframe.contentDocument — so it disables auto-height. Always pair a stricter sandbox with a fixed height. The empty string "" is honored verbatim; only omitting the prop falls back to the computed default.

Props

PropTypeDefaultDescription
htmlstring(required)HTML content to render inside the preview iframe
cssstringundefinedCSS styles applied inside the preview iframe
headstringundefinedRaw HTML injected into <head> (links, meta, fonts)
jsstringundefinedJavaScript executed inside the preview iframe
titlestringundefinedTitle displayed in the preview header bar
heightnumberautoFixed iframe height in pixels. When omitted, height auto-adjusts to content
defaultOpenbooleanfalseShow the source code panel expanded by default
sandboxstringautoiframe sandbox attribute. Omit for the computed default (allow-scripts allow-same-origin with scripts, allow-same-origin without). Pass a stricter value for untrusted content — but disables auto-height when allow-same-origin is dropped, so set height too

Supported Languages

The source code panel uses a lightweight Shiki instance with the following languages:

  • html — for the HTML and Head panels

  • css — for the CSS panel

  • javascript — for the JS panel

Unsupported languages fall back to plain text (no highlighting). This is a smaller subset than the full language list available in standard code blocks.

Notes

  • The preview renders inside an isolated <iframe> with a CSS reset (Tailwind v4 preflight), so styles do not leak in or out.

  • Previews default to sandbox="allow-same-origin" (or sandbox="allow-scripts allow-same-origin" when a js prop or <script> is present) so the iframe height auto-syncs via contentDocument. The srcdoc content is author-controlled MDX. Override with the sandbox prop for untrusted content — see Security & the sandbox prop above.

  • Global resources from settings.htmlPreview are injected before per-component props.

  • The html, css, and js props support template literals with indentation. Leading whitespace is automatically stripped (dedented) in the source code display.

  • Client-side hydration is handled automatically by the component wrapper — no client:load directive needed in MDX.

Revision History

Takeshi TakatsudoCreated: 2026-03-16T16:24:56+09:00Updated: 2026-06-11T17:53:54+09:00

AI Assistant

Ask a question about the documentation.