PreviewFrame
Sandbox iframe with wake, restart, and refresh lifecycle.
PreviewFrame is the workspace’s window into the running app: a sandboxed iframe in quiet chrome — a status dot, a mono URL readout, and refresh / restart / open-in-new-tab actions. The chrome never moves; lifecycle states play out as overlays. While the sandbox wakes, the NeonLoader resolves out of grain over a scrim; on error, a structured panel offers a restart. When the agent finishes an edit, bump reloadSignal and the frame remounts without any chrome motion.
Install
npx shadcn@latest add https://ui.neon.com/r/preview-frame.json
Usage
import { PreviewFrame } from "@/components/neon-ui/preview-frame";
<PreviewFrame
className="h-96"
src="https://sandbox-8f2k.internal.dev"
displaySrc="https://acme-crm.vibe.app"
title="Live app preview"
state={sandbox.state}
reloadSignal={editCount}
onRestart={() => restartSandbox()}
/>;
States
- Ready — the app full bleed; the header dot holds primary.
- Refresh — the built-in refresh remounts the iframe and the glyph turns once (500ms ease-out), so the click reads even when the reload is instant.
- Sleeping — the app dims behind a scrim with a moon, mono
sleeping, and a detail line; the dot rests dim. A click anywhere on the frame (or the wake action) callsonWake. - Waking — a scrim covers the app with the NeonLoader and one mono line; the dot breathes muted. Use it for cold starts, wakes, and post-restart polling.
- Error — the scrim holds with a mono
errorprefix, an optional detail line, and a restart action; the dot cools to destructive. - Reload on signal — increment
reloadSignalfrom outside (e.g. after each agent edit) to remount the frame with zero chrome motion.
Every overlay is absolute — the frame never changes height. The restart button’s hover tells you what it’s about to do: destructive while the sandbox is running, primary when it’s down.
Props
srcstring
The sandbox URL the frame renders.
stringtitlestring
Accessible name for the iframe.
stringdisplaySrc?string
What the header readout shows when the real src is an internal sandbox host.
stringsrcstate?"ready" | "sleeping" | "waking" | "error"
Lifecycle state; everything but ready renders as an overlay.
"ready" | "sleeping" | "waking" | "error""ready"onWake?() => void
Renders the wake action in the sleeping state; the whole scrim is click-to-wake.
() => voidsleepingDetail?string
One line under the sleeping title.
string"Compute suspended after inactivity."reloadSignal?number
Bump to force a reload from outside, e.g. after the agent applies an edit.
number0onRestart?() => void
Renders the restart action in the header and the error panel.
() => voidonRefresh?() => void
Notified after the built-in refresh reloads the frame.
() => voiderrorDetail?string
One line of detail under the error title.
stringwakingLabel?string
Copy under the loader while waking.
string"Waking sandbox"actions?ReactNode
Extra actions rendered before the built-in header buttons.
ReactNodesandbox?string
The iframe sandbox policy.
string"allow-scripts allow-same-origin allow-forms"Accessibility
- The iframe carries the required
title; header actions are labelled icon buttons. - The error overlay is
role="alert"so the failure is announced. - The waking loader exposes status text through NeonLoader’s
role="status". - The refresh spin and overlay fades are static under
prefers-reduced-motion.