Web Preview
Sandboxed iframe surface for live HTML preview. Includes URL bar, optional console output and a context-shared URL state.
Overview
WebPreview is the surface for running an AI-generated page, snippet or live URL inside the chat. The iframe is sandboxed with allow-scripts allow-same-origin allow-forms allow-popups allow-presentation, so it can execute scripts and forms without breaking out of the parent app.
Internally it ships a WebPreviewContext that carries the current url, a setter that fires onUrlChange, and the consoleOpen state. Composition pieces (WebPreviewUrl, WebPreviewBody, WebPreviewConsole) all subscribe to the same context, so typing a URL in the bar reflects in the iframe without prop drilling.
Pair with Artifact when the preview is part of a generated asset, or use it standalone as a live preview tile in a workspace.
Preview
Basic preview with URL bar
'use client';import { WebPreview, WebPreviewBody, WebPreviewNavigation, WebPreviewUrl,} from '@gremorie/rx-artifacts';export function WebPreviewPreview() { return ( <WebPreview defaultUrl="https://gremorie.com" className="max-w-2xl"> <WebPreviewNavigation> <WebPreviewUrl /> </WebPreviewNavigation> <WebPreviewBody src="https://gremorie.com" className="h-[240px]" /> </WebPreview> );}With console output
'use client';import { WebPreview, WebPreviewBody, WebPreviewConsole, WebPreviewNavigation, WebPreviewUrl,} from '@gremorie/rx-artifacts';export function WebPreviewConsolePreview() { return ( <WebPreview defaultUrl="https://gremorie.com" className="max-w-2xl"> <WebPreviewNavigation> <WebPreviewUrl /> </WebPreviewNavigation> <WebPreviewBody src="https://gremorie.com" className="h-[180px]" /> <WebPreviewConsole logs={[ { level: 'log', message: '[ready] dev server on :5020', timestamp: new Date(), }, { level: 'warn', message: 'Slow render on /components/workflow/canvas', timestamp: new Date(), }, ]} /> </WebPreview> );}Installation
bash npx gremorie@latest add rx-web-preview bash pnpm dlx gremorie@latest add rx-web-preview
bash yarn dlx gremorie@latest add rx-web-preview
bash bunx --bun gremorie@latest add rx-web-preview
Brings in rx-display (for Collapsible), rx-forms (for Input and Button) and rx-overlays (for Tooltip) as registry dependencies.
Usage
import {
WebPreview,
WebPreviewBody,
WebPreviewConsole,
WebPreviewNavigation,
WebPreviewUrl,
} from "@gremorie/rx-artifacts";
export function Example({ logs }) {
return (
<WebPreview defaultUrl="https://gremorie.com" onUrlChange={track}>
<WebPreviewNavigation>
<WebPreviewUrl />
</WebPreviewNavigation>
<WebPreviewBody src="https://gremorie.com" />
<WebPreviewConsole logs={logs} />
</WebPreview>
);
}Angular edition planned for Phase 5h. Use the React edition for now, or open an issue to prioritize this primitive.
API
<WebPreview>
| Prop | Type | Default | Description |
|---|---|---|---|
defaultUrl | string | "" | Initial URL. Seeds the internal url state and WebPreviewUrl. |
onUrlChange | (url: string) => void | - | Fires whenever a child calls setUrl (typically on Enter inside WebPreviewUrl). |
className | string | - | Extra classes on the root div. |
Extends all ComponentProps<"div">. Provides WebPreviewContext to all descendants.
Default chrome: flex size-full flex-col rounded-lg border bg-card.
<WebPreviewNavigation>
Header row hosting the URL bar and navigation buttons. Sits at the top with a border-bottom separator.
Extends all ComponentProps<"div">.
<WebPreviewNavigationButton>
| Prop | Type | Default | Description |
|---|---|---|---|
tooltip | string | - | Tooltip label. Required for icon-only buttons. |
onClick | () => void | - | Click handler. |
disabled | boolean | - | Standard Button disabled state. |
Renders a 32x32 ghost Button wrapped in a Tooltip. Use for back / forward / reload affordances.
<WebPreviewUrl>
Controlled or uncontrolled URL input. Reads url from WebPreviewContext and calls setUrl on Enter, which propagates to onUrlChange.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | context url | Override the context value to make the input fully controlled. |
onChange | ChangeEventHandler<HTMLInputElement> | - | Optional override of the change handler. |
onKeyDown | KeyboardEventHandler<HTMLInputElement> | - | Called alongside the internal Enter handler. |
Extends all Input props from @gremorie/rx-forms.
<WebPreviewBody>
The sandboxed iframe.
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | context url | Override the iframe source. Falls back to the context URL. |
loading | ReactNode | - | Optional skeleton or spinner rendered alongside the iframe. |
className | string | - | Extra classes on the iframe. |
Hardcoded sandbox: allow-scripts allow-same-origin allow-forms allow-popups allow-presentation. Title: "Preview".
<WebPreviewConsole>
| Prop | Type | Default | Description |
|---|---|---|---|
logs | Array<{ level, message, timestamp }> | [] | Log entries. level is "log" | "warn" | "error", timestamp is a Date. |
children | ReactNode | - | Extra content rendered below the logs (raw HTML console, debug controls, etc.). |
className | string | - | Extra classes on the Collapsible root. |
A Collapsible that opens or closes via the shared consoleOpen state. Empty logs surface a "No console output" placeholder. The trigger + content are rendered internally - do NOT compose with separate WebPreviewConsoleTrigger / WebPreviewConsoleContent, those subcomponents do not exist.
Composition
<WebPreview>is the wrapper that providesWebPreviewContext. Hold the URL state here.<WebPreviewNavigation>is the address-bar row. DropWebPreviewNavigationButtons for back / forward / reload, thenWebPreviewUrlas the flex-1 input.<WebPreviewBody>fills the remaining height with the iframe.<WebPreviewConsole>docks to the bottom and expands upward when opened. The "console open" state is shared, so a custom trigger elsewhere can also expand it.
Variations
Read-only preview
When the URL is fixed (an MDX demo, a generated asset), skip the nav row entirely.
<WebPreview defaultUrl="/preview/landing">
<WebPreviewBody src="/preview/landing" className="h-[480px]" />
</WebPreview>Browser chrome with back / forward
Add WebPreviewNavigationButtons before the URL input for a more browser-like feel.
<WebPreview defaultUrl="/preview/landing">
<WebPreviewNavigation>
<WebPreviewNavigationButton tooltip="Back" onClick={back}>
<ArrowLeftIcon className="size-4" />
</WebPreviewNavigationButton>
<WebPreviewNavigationButton tooltip="Forward" onClick={forward}>
<ArrowRightIcon className="size-4" />
</WebPreviewNavigationButton>
<WebPreviewNavigationButton tooltip="Reload" onClick={reload}>
<RotateCwIcon className="size-4" />
</WebPreviewNavigationButton>
<WebPreviewUrl />
</WebPreviewNavigation>
<WebPreviewBody src="/preview/landing" />
</WebPreview>Live console for debugging generated code
Stream logs from the iframe (e.g. via postMessage) and pass them through logs. Errors render in destructive color, warnings in yellow, regular logs in foreground.
<WebPreview defaultUrl="about:blank">
<WebPreviewBody srcDoc={generatedHtml} className="h-[280px]" />
<WebPreviewConsole logs={consoleLogs} />
</WebPreview>Accessibility
- Iframe title: The
<iframe>always carriestitle="Preview"so screen readers announce it. Override via the spread props if you want a more specific title per surface. - Sandbox: The strict sandbox keeps the iframe from breaking out of the parent app. If you need a relaxed sandbox (e.g. allowing top navigation), pass
sandboxexplicitly to override. - Keyboard: Focus enters the URL input via Tab. Enter submits the new URL. The console trigger is a real
Button, so it activates on Enter / Space. - Console color: Errors are not conveyed by color alone. Each entry retains its raw text plus the timestamp, so the level can be inferred from the message even on monochrome displays. If you want full a11y for the level, prepend the level to the message text.
- Screen readers: Empty console state announces "No console output", so users do not get a silent collapsed surface.
Related
- Artifact - card chrome that often hosts a
WebPreview - Code Block - the source code that this preview renders
- Open in Chat - hand a generated page back to the chat for iteration