Skip to content
MediaImage generationMenu

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.

Four variations queue, reveal from the top as they sharpen, then upscale or vary the one you pick.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/image-generation.json

Usage

tsx
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.

Queued with a position, generating with and without a partial image, done, failed and content blocked.

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.

Wide, square and tall frames. Tall single images cap their height.

Props

PropTypeNotes
promptstringShown above the images with a copy button
status"queued" | "generating" | "done" | "failed" | "blocked"blocked means the provider declined the prompt
imagesGeneratedImage[]src, previewSrc, alt, width, height, filename, per-image progress
countnumberVariations, 1 to 4. Defaults to the number of images
aspectRatio"W:H" | numberDefaults to "1:1"
progressnumber0 to 100. Leave it out when the provider reports none
queuePositionnumber1 means next
statusTextstringOverrides Creating image, Adding details, Finishing
settingsReactNode[]Facts after the aspect ratio: model, style, seed
showPromptbooleanTurn off inside a tool call. Default on
errorText, blockedTextReactNodeSay what happened and how to fix it
onRetry, onCancel, onEditPrompt() => voidEach shows its button
onUpscale, onVary, onUseAsReference(image, index) => voidAct on the selected variation. Each shows its button
onDownload(image, index) => voidReplaces the built-in download link
busyAction"upscale" | "vary" | "reference"Spinner in that button
selectedIndex, defaultSelectedIndex, onSelectedIndexChangeWhich variation the actions apply to

GenerationFrame, GenerationGrain and GenerationNotice are exported for your own media layouts. Video generation is built on them.

Keyboard

KeyAction
TabInto the variations, then the actions
← → ↑ ↓Pick a variation. Left and right mirror in RTL
Home, EndFirst or last variation
EnterOpen the picked variation full screen
← → in the viewerPage through the variations

Accessibility

  • Finished variations are a radiogroup with roving focus. A single image is one button that opens the viewer.
  • While generating, the status line is a progressbar with 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-path from 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.