Files
Hayden Bleasel cf793dd446 Elements domain (#339)
* Remove microfrontends

* Remove /elements subpath, update URL

* Fix local paths

* Remove custom AI SDK navbar

* Update global.css

* Restore docs in path

* Restore Geistdocs homepage

* Split up docs, components and examples

* New routes for components and examples

* IA fixes

* Update hero, start adding components to homepage

* Experimenting with hero

* Update pnpm-lock.yaml

* Add new docs

* Update geistdocs.tsx

* Update examples, add new IDE example

* Create source-code.tsx

* Cleanup ide.mdx

* Cleanup examples

* Update homepage content

* Misc fixes

* Update elements-installer.tsx

* Update meta.json

* Deprecate Loader component for shadcn/ui Spinner

* Add changeset files

* Update homepage and templates

* Update code-block.tsx

* Misc fixes

* Update callout.tsx

* Remove hero badge

* Resolves #328

* Build fixes

* Update README.md
2026-01-31 15:31:31 -08:00

247 lines
6.3 KiB
Plaintext

---
title: Web Preview
description: A composable component for previewing the result of a generated UI, with support for live examples and code display.
path: elements/components/web-preview
---
The `WebPreview` component provides a flexible way to showcase the result of a generated UI component, along with its source code. It is designed for documentation and demo purposes, allowing users to interact with live examples and view the underlying implementation.
<Preview path="web-preview" />
## Installation
<ElementsInstaller path="web-preview" />
## Usage with AI SDK
Build a simple v0 clone using the [v0 Platform API](https://v0.dev/docs/api/platform).
Install the `v0-sdk` package:
```package-install
npm i v0-sdk
```
Add the following component to your frontend:
```tsx title="app/page.tsx"
'use client';
import {
WebPreview,
WebPreviewBody,
WebPreviewNavigation,
WebPreviewUrl,
} from '@/components/ai-elements/web-preview';
import { useState } from 'react';
import {
Input,
PromptInputTextarea,
PromptInputSubmit,
} from '@/components/ai-elements/prompt-input';
import { Spinner } from '@/components/ui/spinner';
const WebPreviewDemo = () => {
const [previewUrl, setPreviewUrl] = useState('');
const [prompt, setPrompt] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!prompt.trim()) return;
setPrompt('');
setIsGenerating(true);
try {
const response = await fetch('/api/v0', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt }),
});
const data = await response.json();
setPreviewUrl(data.demo || '/');
console.log('Generation finished:', data);
} catch (error) {
console.error('Generation failed:', error);
} finally {
setIsGenerating(false);
}
};
return (
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
<div className="flex flex-col h-full">
<div className="flex-1 mb-4">
{isGenerating ? (
<div className="flex flex-col items-center justify-center h-full">
<Spinner />
<p className="mt-4 text-muted-foreground">
Generating app, this may take a few seconds...
</p>
</div>
) : previewUrl ? (
<WebPreview defaultUrl={previewUrl}>
<WebPreviewNavigation>
<WebPreviewUrl />
</WebPreviewNavigation>
<WebPreviewBody src={previewUrl} />
</WebPreview>
) : (
<div className="flex items-center justify-center h-full text-muted-foreground">
Your generated app will appear here
</div>
)}
</div>
<Input
onSubmit={handleSubmit}
className="w-full max-w-2xl mx-auto relative"
>
<PromptInputTextarea
value={prompt}
placeholder="Describe the app you want to build..."
onChange={(e) => setPrompt(e.currentTarget.value)}
className="pr-12 min-h-[60px]"
/>
<PromptInputSubmit
status={isGenerating ? 'streaming' : 'ready'}
disabled={!prompt.trim()}
className="absolute bottom-1 right-1"
/>
</Input>
</div>
</div>
);
};
export default WebPreviewDemo;
```
Add the following route to your backend:
```ts title="app/api/v0/route.ts"
import { v0 } from 'v0-sdk';
export async function POST(req: Request) {
const { prompt }: { prompt: string } = await req.json();
const result = await v0.chats.create({
system: 'You are an expert coder',
message: prompt,
modelConfiguration: {
modelId: 'v0-1.5-sm',
imageGenerations: false,
thinking: false,
},
});
return Response.json({
demo: result.demo,
webUrl: result.webUrl,
});
}
```
## Features
- Live preview of UI components
- Composable architecture with dedicated sub-components
- Responsive design modes (Desktop, Tablet, Mobile)
- Navigation controls with back/forward functionality
- URL input and example selector
- Full screen mode support
- Console logging with timestamps
- Context-based state management
- Consistent styling with the design system
- Easy integration into documentation pages
## Props
### `<WebPreview />`
<TypeTable
type={{
defaultUrl: {
description: 'The initial URL to load in the preview.',
type: 'string',
default: '""',
},
onUrlChange: {
description: 'Callback fired when the URL changes.',
type: '(url: string) => void',
},
'...props': {
description: 'Any other props are spread to the root div.',
type: 'React.HTMLAttributes<HTMLDivElement>',
},
}}
/>
### `<WebPreviewNavigation />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the navigation container.',
type: 'React.HTMLAttributes<HTMLDivElement>',
},
}}
/>
### `<WebPreviewNavigationButton />`
<TypeTable
type={{
tooltip: {
description: 'Tooltip text to display on hover.',
type: 'string',
},
'...props': {
description: 'Any other props are spread to the underlying shadcn/ui Button component.',
type: 'React.ComponentProps<typeof Button>',
},
}}
/>
### `<WebPreviewUrl />`
<TypeTable
type={{
'...props': {
description: 'Any other props are spread to the underlying shadcn/ui Input component.',
type: 'React.ComponentProps<typeof Input>',
},
}}
/>
### `<WebPreviewBody />`
<TypeTable
type={{
loading: {
description: 'Optional loading indicator to display over the preview.',
type: 'React.ReactNode',
},
'...props': {
description: 'Any other props are spread to the underlying iframe.',
type: 'React.IframeHTMLAttributes<HTMLIFrameElement>',
},
}}
/>
### `<WebPreviewConsole />`
<TypeTable
type={{
logs: {
description: 'Console log entries to display in the console panel.',
type: 'Array<{ level: "log" | "warn" | "error"; message: string; timestamp: Date }>',
},
'...props': {
description: 'Any other props are spread to the root div.',
type: 'React.HTMLAttributes<HTMLDivElement>',
},
}}
/>