Image generation
A generated image in a conversation: prompt and settings, one to four variations at their final shape, a blur-to-sharp scan reveal while the model works, and actions for the one you pick.
On this page
Installation
Usage
import { ImageGeneration } from "@/components/ui/chatcn/image-generation"
<ImageGeneration
prompt={job.prompt}
status={job.status} // "queued" | "generating" | "done" | "failed" | "blocked"
queuePosition={job.queuePosition}
progress={job.progress}
aspectRatio="3:2"
count={4}
images={job.images} // pass the latest partial image as src while generating
settings={["Halftone 2", `Seed ${job.seed}`]}
onCancel={cancel}
onUpscale={(image, index) => upscale(image)}
onVary={(image) => vary(image)}
onUseAsReference={(image) => attach(image)}
/>Every frame takes its aspect ratio from the first paint, so nothing shifts between queued, generating and done. Providers that stream partial images (OpenAI partial_images, for example) pass the newest one as src: it shows blurred behind the reveal and sharp above the scan line. Providers that send nothing until the end leave src out, and the part above the line lifts a shade instead.
Examples
Every state
Queued with a position, generating with and without a partial image, done, failed with a retry, and a prompt the provider declined. The declined state stays calm: a shield, not a red error, and a sentence that says what to change.
Aspect ratios
Pass "16:9", "1:1", "9:16" or a number. A tall single image caps its height at 32 rem so it never fills the screen. Four portrait variations sit in one row; four others make a square.
Props
| Prop | Type | Notes |
|---|---|---|
prompt | string | Shown above the images with a copy button |
status | "queued" | "generating" | "done" | "failed" | "blocked" | blocked means the provider declined the prompt |
images | GeneratedImage[] | src, previewSrc, alt, width, height, filename, per-image progress |
count | number | Variations, 1 to 4. Defaults to the number of images |
aspectRatio | "W:H" | number | Defaults to "1:1" |
progress | number | 0 to 100. Leave it out when the provider reports none |
queuePosition | number | 1 means next |
statusText | string | Overrides Creating image, Adding details, Finishing |
settings | ReactNode[] | Facts after the aspect ratio: model, style, seed |
showPrompt | boolean | Turn off inside a tool call. Default on |
errorText, blockedText | ReactNode | Say what happened and how to fix it |
onRetry, onCancel, onEditPrompt | () => void | Each shows its button |
onUpscale, onVary, onUseAsReference | (image, index) => void | Act on the selected variation. Each shows its button |
onDownload | (image, index) => void | Replaces the built-in download link |
busyAction | "upscale" | "vary" | "reference" | Spinner in that button |
selectedIndex, defaultSelectedIndex, onSelectedIndexChange | Which variation the actions apply to |
GenerationFrame, GenerationGrain and GenerationNotice are exported for your own media layouts. Video generation is built on them.
Keyboard
| Key | Action |
|---|---|
Tab | Into the variations, then the actions |
← → ↑ ↓ | Pick a variation. Left and right mirror in RTL |
Home, End | First or last variation |
Enter | Open the picked variation full screen |
← → in the viewer | Page through the variations |
Accessibility
- Finished variations are a
radiogroupwith roving focus. A single image is one button that opens the viewer. - While generating, the status line is a
progressbarwith the percentage, and the frames are hidden from screen readers until there is something to describe. - Status changes are announced once in a polite live region: queued with the position, ready, failed or not created. Progress is never announced per step.
- Every frame carries a hairline that survives forced colors, and errors pair the red icon with words.
Motion
- The reveal is a
clip-pathfrom the top that follows progress over 300 ms, with a 1 px scan line on the edge. The image behind it is blurred and sharpens as progress grows. There is no shimmer sweep anywhere. - On completion the clip opens fully, the blur fades out, and the image settles from 1.02 to 1 over 300 ms.
- Images restored from history fade in over 200 ms with no reveal.
- Picking a variation with the keyboard moves the ring instantly. Under reduced motion the clip, scan and settle are instant, and only short fades remain.