|
| 1 | +--- |
| 2 | +title: Web Preview |
| 3 | +description: A composable component for previewing the result of a generated UI, with support for live examples and code display. |
| 4 | +icon: lucide:globe |
| 5 | +--- |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +:::ComponentLoader{label="Web Preview" componentName="WebPreview"} |
| 10 | +::: |
| 11 | + |
| 12 | +## Install using CLI |
| 13 | + |
| 14 | +:::tabs{variant="card"} |
| 15 | + ::div{label="ai-elements-vue"} |
| 16 | + ```sh |
| 17 | + npx ai-elements-vue@latest add web-preview |
| 18 | + ``` |
| 19 | + :: |
| 20 | + ::div{label="shadcn-vue"} |
| 21 | + |
| 22 | + ```sh |
| 23 | + npx shadcn-vue@latest add https://registry.ai-elements-vue.com/web-preview.json |
| 24 | + ``` |
| 25 | + :: |
| 26 | +::: |
| 27 | + |
| 28 | +## Install Manually |
| 29 | +<!-- TODO: Add code snippets for manual installation --> |
| 30 | + |
| 31 | +## Usage with AI SDK |
| 32 | + |
| 33 | +Build a simple v0 clone using the [v0 Platform API](https://v0.dev/docs/api/platform). |
| 34 | + |
| 35 | +Install the `v0-sdk` package: |
| 36 | + |
| 37 | +```package-install |
| 38 | +npm i v0-sdk |
| 39 | +``` |
| 40 | + |
| 41 | +Add the following component to your frontend: |
| 42 | +<!-- TOOD: Using Vue 3 example --> |
| 43 | +```tsx title="app/page.tsx" |
| 44 | +'use client' |
| 45 | + |
| 46 | +import { useState } from 'react' |
| 47 | +import { |
| 48 | + Input, |
| 49 | + PromptInputSubmit, |
| 50 | + PromptInputTextarea, |
| 51 | +} from '@/components/ai-elements/prompt-input' |
| 52 | +import { |
| 53 | + WebPreview, |
| 54 | + WebPreviewBody, |
| 55 | + WebPreviewNavigation, |
| 56 | + WebPreviewUrl, |
| 57 | +} from '@/components/ai-elements/web-preview' |
| 58 | +import { Loader } from '../ai-elements/loader' |
| 59 | + |
| 60 | +function WebPreviewDemo() { |
| 61 | + const [previewUrl, setPreviewUrl] = useState('') |
| 62 | + const [prompt, setPrompt] = useState('') |
| 63 | + const [isGenerating, setIsGenerating] = useState(false) |
| 64 | + |
| 65 | + const handleSubmit = async (e: React.FormEvent) => { |
| 66 | + e.preventDefault() |
| 67 | + if (!prompt.trim()) |
| 68 | + return |
| 69 | + setPrompt('') |
| 70 | + |
| 71 | + setIsGenerating(true) |
| 72 | + try { |
| 73 | + const response = await fetch('/api/v0', { |
| 74 | + method: 'POST', |
| 75 | + headers: { 'Content-Type': 'application/json' }, |
| 76 | + body: JSON.stringify({ prompt }), |
| 77 | + }) |
| 78 | + |
| 79 | + const data = await response.json() |
| 80 | + setPreviewUrl(data.demo || '/') |
| 81 | + console.log('Generation finished:', data) |
| 82 | + } |
| 83 | + catch (error) { |
| 84 | + console.error('Generation failed:', error) |
| 85 | + } |
| 86 | + finally { |
| 87 | + setIsGenerating(false) |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + return ( |
| 92 | + <div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]"> |
| 93 | + <div className="flex flex-col h-full"> |
| 94 | + <div className="flex-1 mb-4"> |
| 95 | + {isGenerating |
| 96 | + ? ( |
| 97 | + <div className="flex flex-col items-center justify-center h-full"> |
| 98 | + <Loader /> |
| 99 | + <p className="mt-4 text-muted-foreground"> |
| 100 | + Generating app, this may take a few seconds... |
| 101 | + </p> |
| 102 | + </div> |
| 103 | + ) |
| 104 | + : previewUrl |
| 105 | + ? ( |
| 106 | + <WebPreview defaultUrl={previewUrl}> |
| 107 | + <WebPreviewNavigation> |
| 108 | + <WebPreviewUrl /> |
| 109 | + </WebPreviewNavigation> |
| 110 | + <WebPreviewBody src={previewUrl} /> |
| 111 | + </WebPreview> |
| 112 | + ) |
| 113 | + : ( |
| 114 | + <div className="flex items-center justify-center h-full text-muted-foreground"> |
| 115 | + Your generated app will appear here |
| 116 | + </div> |
| 117 | + )} |
| 118 | + </div> |
| 119 | + |
| 120 | + <Input |
| 121 | + onSubmit={handleSubmit} |
| 122 | + className="w-full max-w-2xl mx-auto relative" |
| 123 | + > |
| 124 | + <PromptInputTextarea |
| 125 | + value={prompt} |
| 126 | + placeholder="Describe the app you want to build..." |
| 127 | + onChange={e => setPrompt(e.currentTarget.value)} |
| 128 | + className="pr-12 min-h-[60px]" |
| 129 | + /> |
| 130 | + <PromptInputSubmit |
| 131 | + status={isGenerating ? 'streaming' : 'ready'} |
| 132 | + disabled={!prompt.trim()} |
| 133 | + className="absolute bottom-1 right-1" |
| 134 | + /> |
| 135 | + </Input> |
| 136 | + </div> |
| 137 | + </div> |
| 138 | + ) |
| 139 | +} |
| 140 | + |
| 141 | +export default WebPreviewDemo |
| 142 | +``` |
| 143 | + |
| 144 | +Add the following route to your backend: |
| 145 | + |
| 146 | +<!-- TOOD: Using Nuxt example --> |
| 147 | + |
| 148 | +```ts title="app/api/v0/route.ts" |
| 149 | +import { v0 } from 'v0-sdk' |
| 150 | + |
| 151 | +export async function POST(req: Request) { |
| 152 | + const { prompt }: { prompt: string } = await req.json() |
| 153 | + |
| 154 | + const result = await v0.chats.create({ |
| 155 | + system: 'You are an expert coder', |
| 156 | + message: prompt, |
| 157 | + modelConfiguration: { |
| 158 | + modelId: 'v0-1.5-sm', |
| 159 | + imageGenerations: false, |
| 160 | + thinking: false, |
| 161 | + }, |
| 162 | + }) |
| 163 | + |
| 164 | + return Response.json({ |
| 165 | + demo: result.demo, |
| 166 | + webUrl: result.webUrl, |
| 167 | + }) |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +## Features |
| 172 | + |
| 173 | +- Live preview of UI components |
| 174 | +- Composable architecture with dedicated sub-components |
| 175 | +- Responsive design modes (Desktop, Tablet, Mobile) |
| 176 | +- Navigation controls with back/forward functionality |
| 177 | +- URL input and example selector |
| 178 | +- Full screen mode support |
| 179 | +- Console logging with timestamps |
| 180 | +- Context-based state management |
| 181 | +- Consistent styling with the design system |
| 182 | +- Easy integration into documentation pages |
| 183 | + |
| 184 | +## Props |
| 185 | + |
| 186 | +### `<WebPreview />` |
| 187 | + |
| 188 | +::::field-group |
| 189 | + ::field{name="defaultUrl" type="string" defaultValue="''"} |
| 190 | + The initial URL to load in the preview. |
| 191 | + :: |
| 192 | + ::field{name="onUrlChange" type="(url: string) => void"} |
| 193 | + Callback fired when the URL changes. |
| 194 | + :: |
| 195 | + ::field{name="...props" type="HTMLAttributes"} |
| 196 | + Any other props are spread to the root div. |
| 197 | + :: |
| 198 | +:::: |
| 199 | + |
| 200 | +### `<WebPreviewNavigation />` |
| 201 | + |
| 202 | +::::field-group |
| 203 | + ::field{name="...props" type="HTMLAttributes"} |
| 204 | + Any other props are spread to the navigation container. |
| 205 | + :: |
| 206 | +:::: |
| 207 | + |
| 208 | +### `<WebPreviewNavigationButton />` |
| 209 | + |
| 210 | +::::field-group |
| 211 | + ::field{name="tooltip" type="string"} |
| 212 | + Tooltip text to display on hover. |
| 213 | + :: |
| 214 | + ::field{name="...props" type="typeof Button"} |
| 215 | + Any other props are spread to the underlying shadcn/ui Button component. |
| 216 | + :: |
| 217 | +:::: |
| 218 | + |
| 219 | +### `<WebPreviewUrl />` |
| 220 | + |
| 221 | +::::field-group |
| 222 | + ::field{name="...props" type="typeof Input"} |
| 223 | + Any other props are spread to the underlying shadcn/ui Input component. |
| 224 | + :: |
| 225 | +:::: |
| 226 | + |
| 227 | +### `<WebPreviewBody />` |
| 228 | + |
| 229 | +::::field-group |
| 230 | + ::field{name="loading" type="React.ReactNode"} |
| 231 | + Optional loading indicator to display over the preview. |
| 232 | + :: |
| 233 | + ::field{name="...props" type="IframeHTMLAttributes"} |
| 234 | + Any other props are spread to the underlying iframe. |
| 235 | + :: |
| 236 | +:::: |
| 237 | + |
| 238 | +### `<WebPreviewConsole />` |
| 239 | + |
| 240 | +::::field-group |
| 241 | + ::field{name="logs" type='Array<{ level: "log" | "warn" | "error"; message: string; timestamp: Date }>'} |
| 242 | + Console log entries to display in the console panel. |
| 243 | + :: |
| 244 | + ::field{name="...props" type="HTMLAttributes"} |
| 245 | + Any other props are spread to the root div. |
| 246 | + :: |
| 247 | +:::: |
0 commit comments