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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
useWebPreview
Hook for reading and mutating the preview state from any child. Must be called inside <WebPreview>.
State:
urlis the current URL displayed in the iframe.viewportis the active viewport width in pixels, ornullfor fit.activePanelis the currently open panel id, ornullwhen the dock is closed. Use this for trigger state and to drivesetActivePanelfrom outside the built-in triggers.displayedPanelis the id of the panel currently mounted in the dock. LagsactivePanelduring the close transition so panel content stays available while the height animation runs.reloadKeyincrements each timereload()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 withnull.setActivePanel(next)opens a panel by id, or closes the dock withnull.reload()bumps the reload counter.