Web Preview

An iframe-based site preview with a chrome header and a dockable panel area for things like console or network views.

Anatomy

WebPreview is a context provider. It owns the URL, the viewport width, the active dock panel, and a reload counter. Every part below reads from that context.

TSX

The chrome layout, the viewport mode, and the panel layout are not baked into a fixed template, so the demo composes them with regular divs. The same building blocks cover a one-button cycle (fit → tablet → mobile) and a multi-tab dock.

For agent-driven previews where the parent owns the URL, pass url and listen to onUrlChange. The component falls back to its address bar when uncontrolled.

API

Reference for each part of the component, including its available props and behavior.

WebPreview

The root and the context provider. Owns the URL, viewport width, active panel, and reload counter, and exposes them through useWebPreview. Renders a plain <div> with the kit's surface treatment (bg-surface, ring, rounded outer) and flex flex-col so the header, canvas, and dock stack naturally.

URL submissions from the address bar are normalized — strings without a scheme get https:// prepended — before onUrlChange fires.

Prop

WebPreviewHeader

The top row. A flex container styled for the kit's chrome look. Drop in any combination of WebPreviewReload, WebPreviewAddress, WebPreviewOpen, viewport controls, or your own buttons.

Prop

WebPreviewAddress

The URL input. Shows the live URL when blurred, switches to an editable draft on focus, and commits on Enter. Escape cancels the edit and restores the previous value. Submitting blurs the input so focus returns to the page.

The address bar is optional — for agent-driven previews where navigation happens from outside the component, leave it out and drive the URL through the url prop.

Prop

WebPreviewReload

A headless trigger that bumps an internal reload counter. The counter is keyed into the iframe so the same URL forces a fresh load. Disabled while no URL is set.

Prop

WebPreviewOpen

A headless anchor that opens the current URL in a new tab. Renders as <a> by default with target="_blank" and rel="noreferrer noopener" wired up.

Prop

WebPreviewContent

The canvas. Renders a sandboxed <iframe> (allow-scripts allow-same-origin allow-forms allow-popups) inside a fixed-height wrapper (h-96 by default). Pass className to override the wrapper's height — for example h-[600px] or h-full when the parent provides its own height. The iframe always fills the wrapper.

When a viewport is set, the iframe is constrained to that width and centered inside the canvas. The width animates between values so toggling tablet → mobile feels smooth.

For cross-origin previews the iframe can't be inspected from JavaScript, so in-iframe navigation isn't tracked back into the address bar. Drive navigation through the url prop when the previewed page is cooperative.

Prop

WebPreviewPanelTrigger

A headless toggle for a dock panel. Clicking it sets activePanel to its panelId. Clicking the active trigger collapses the dock. data-active is set when this panel is open, so styling both states from one render element is straightforward.

Triggers can live anywhere in the tree — header, a bottom bar, or somewhere outside WebPreview entirely (anywhere useWebPreview works).

Prop

WebPreviewPanels

The dock container. Wraps a base-ui Collapsible and animates height from 0 to the panel's measured height when open. The root uses display: contents so a closed dock doesn't sit as a flex slot in the parent and steal a gap-* from it.

A separate displayedPanel state lags activePanel by one transition: when the dock closes, displayedPanel keeps the last id so the inner content stays mounted until the height animation finishes. Without it the inner panel would unmount on click and the close animation would have nothing to transition from.

Prop

WebPreviewPanel

A single panel slot. Renders only when displayedPanel matches its id, so switching panels is a content swap inside the same open dock — no double animation. Content is your call: a styled list, a virtualized log view, a chart, whatever the panel represents.

Prop

useWebPreview

Hook for reading and mutating the preview state from any child. Must be called inside <WebPreview>.

State:

  • url is the current URL displayed in the iframe.
  • viewport is the active viewport width in pixels, or null for fit.
  • activePanel is the currently open panel id, or null when the dock is closed. Use this for trigger state and to drive setActivePanel from outside the built-in triggers.
  • displayedPanel is the id of the panel currently mounted in the dock. Lags activePanel during the close transition so panel content stays available while the height animation runs.
  • reloadKey increments each time reload() is called. Keyed into the iframe to force a refresh.

Actions:

  • setUrl(next) navigates the iframe to a new URL. Skips normalization, so pass a fully-qualified URL.
  • setViewport(next) sets the iframe width or clears it with null.
  • setActivePanel(next) opens a panel by id, or closes the dock with null.
  • reload() bumps the reload counter.
TSX