Image generation

Pro

An image studio where each image forms on a dot matrix: a soft halftone wave breathes across the grid, the dots take the colours of the coming photo and grow into a coloured halftone, then blur away into the sharp photo. One large canvas with a mono size label, a filmstrip of four variations, a prompt bar with model and ratio, and upscale with a sweeping dot band, vary or download, with real provider marks and a built-in simulation.

Live · keyboard ready

Describe an image, then generate

1024 × 1024
  • A text to image feature inside a product: marketing asset tools, listing photo helpers, slide or doc illustration.
  • Any flow where people compare a few candidates and continue with one (upscale, vary, download).
  • Prototyping an image model integration before the backend exists, using the built in simulation.
  • A full canvas editor with layers, masks and inpainting; build on a canvas library instead.
  • Drawing a sketch to turn into an image; use sketch-to-image.
  • Showing a single image with a loading state and no generation controls; use blur-image.

Installation

Pro source and install commands unlock with a Pro plan.

example.tsx
import { ImageGeneration } from "@/components/arc/image-generation/image-generation"; export function Studio() {  return <ImageGeneration    onGenerate={async (request, { signal, onProgress }) => {      const job = await fetch("/api/images", {        method: "POST",        signal,        body: JSON.stringify({ prompt: request.prompt, model: request.model.id, size: `${request.aspect.width}x${request.aspect.height}`, seedFrom: request.variationOf?.id }),      }).then(response => response.json());      onProgress(1);      return { id: job.id, src: job.url, alt: request.prompt, width: request.aspect.width, height: request.aspect.height };    }}    onDownload={image => window.open(image.src)}  />;}

API reference

2 parts. The first is the root.

ImageGeneration

An image studio card: one large canvas shows the chosen image, a filmstrip of four variations sits under it beside Upscale, Vary and Download, and a prompt bar with model and aspect chips closes the card. Each image forms on a dot matrix: a soft halftone wave breathes across the grid, the dots take the colours of the coming photo and grow into a coloured halftone, then blur away into the sharp photo. A mono size label sits top right and a status pill with a 3×3 dot spinner bottom left. With no props it runs a local simulation on sample photos.

PropTypeDefaultDescription
promptstring–Controlled prompt. Pair with onPromptChange.
defaultPromptstringsample golden hour landscape promptStarting prompt when uncontrolled.
onPromptChange(prompt: string) => void–Fires as the person types or picks a suggestion.
modelsImageGenerationModel[]imageGenerationModelsModels offered as chips: { id, name, mark? }. The chosen chip shows its name, the others collapse to their 16px mark with a tooltip.
modelstring–Controlled model id. Pair with onModelChange.
defaultModelstringfirst modelStarting model id when uncontrolled.
onModelChange(id: string) => void–Fires when a model chip is chosen.
aspectRatiosImageGenerationAspect[]1:1, 4:5, 16:9Output shapes: { id, label, width, height }. Width and height are the pixels a generation returns, drive the canvas shape and show in the size label.
aspectRatiostring–Controlled aspect id. Pair with onAspectRatioChange.
defaultAspectRatiostringfirst ratioStarting aspect id when uncontrolled.
onAspectRatioChange(id: string) => void–Fires when an aspect chip is chosen. Before the first generation the canvas springs to the new shape; after that the shape applies to the next generation and the current batch keeps its own.
onGenerate(request: ImageGenerationRequest, context: { signal: AbortSignal; onProgress(progress: number, preview?: string): void }) => Promise<ImageGenerationImage>simulateImageGenerationProduces one image. Called once per variation, in parallel, for Generate, Vary (request.variationOf is set) and Retry. Report progress from 0 to 1, optionally with a preview URL: the dots sample their colours from it and dissolve into it over the last fifth of the run (without one, the dots stay neutral and the image resolves from soft to sharp when it arrives); without reports progress estimates. Reject to mark that variation failed; the signal aborts on Stop or Escape.
onUpscale(image: ImageGenerationImage, context: ImageGenerationContext) => Promise<ImageGenerationImage>simulateImageUpscaleUpscales the image on the canvas. Return the new image with its width and height; the size label updates to it.
onDownload(image: ImageGenerationImage) => void–Replaces the built in download, which saves image.src with a file name built from the prompt, index and size.
onSelect(image: ImageGenerationImage | null) => void–Fires with the image on the canvas: when the person picks a finished variation, when the shown variation finishes, with the upscaled image once an upscale finishes, and with null when a new batch starts.
suggestionsstring[]three sample promptsPrompts offered on the empty canvas before the first generation.
autoGeneratebooleantrue with the simulation, false with onGenerateGenerates once when the card first scrolls into view, unless someone has already touched it.
placeholderstring"Describe the image you want"Prompt placeholder.
classNamestring–Class on the card.

simulateImageGeneration

The built in generator, exported so you can wrap it in tests or demos. Picks sample photos that match the prompt, reports the photo as the preview and frame by frame progress over about four and a half to six seconds. Nothing is sent anywhere.

No props.

Cmd/Ctrl+Enter
Generate from the prompt field.
Arrow keys
Move between model chips or aspect chips, and between variations in the filmstrip (each one shows on the canvas as focus lands).
Escape
Stop a running generation; finished images are kept.
Tab
The chosen variation, Upscale, Vary, Download, then the prompt, model, aspect and Generate.
  • Model and aspect chips and the filmstrip are radio groups with a roving tab stop; collapsed model chips keep their name as the accessible label and in a tooltip.
  • The canvas is labelled with the image description once done, or with the variation's state while it generates, fails or stops; each thumbnail names its number and state.
  • A polite live region reports starts, finishes, failures, picks, upscales and downloads, and says when results are simulated.
  • Status is never color alone: failures pair a danger icon with "Failed" and the error, stops a square icon with "Stopped", the chosen thumbnail an accent ring with aria-checked, progress a spinner with a percentage.
  • Icon only actions at narrow widths keep Upscale, Vary and Download as their accessible names.
  • Each image loads on a canvas of small neutral dots, drawn at device pixel ratio. A soft radial halftone wave breathes and wanders across the grid: dots under it grow and darken, and a blurred twin of them glows like a lens.
  • From 20% the dots take the colour of the coming photo at their cell (the preview downscaled to the grid, cover fitted), showing first under the wave, and grow by the photo's darkness, so by 78% the image reads as a coloured halftone.
  • Over the last fifth the dots blur and fade while the photo sharpens out of them from 12px blur and 4% zoom. The final frame is the photo, and the real <img> lands on it without a cut; an image that differs from the preview resolves from soft to sharp in 480ms.
  • The filmstrip thumbnails run their own small dot fields, so all four variations show their progress at once. Picking another variation crossfades the canvas in 200ms.
  • Upscale sweeps a vertical band of halftone dots in the photo's colours left to right: soft ahead of it, sharp behind it, the dots and their blurred twin in it, all from one motion value. The size label crossfades to the new size.
  • The status pill carries a 3×3 dot spinner whose dots light in turn around the ring, with a mono percentage; pills crossfade by key so a label never resizes in a frame.
  • The canvas springs to a new aspect ratio; a container resize cuts straight to the new size. Labels that change inside buttons (Generate and Stop, Upscale, Upscaling, Upscaled, Download and Saved) are stacked in one cell and crossfade, so no button changes width.
  • Reduced motion draws a still dot grid with the halftone glow at rest, crossfades the photo in by progress, keeps the spinner still and drops the upscale sweep.
  • The card fills its container up to 600px. The canvas keeps the batch's ratio and caps its height at 440px, so portrait shapes stay compact.
  • Below 500px of card width the actions drop their labels to icons, so the four thumbnails always fit beside them; thumbnails shrink and stay square.
  • Below 440px the model chips and Generate share the first row of the prompt bar and the ratio chips move to a second.
  • Progress is written straight to motion values, the canvas and styles; React re-renders only on state changes (preview arrives, image finishes).
  • The large canvas draws about four thousand dots per frame with one 2D context plus a blurred twin for the wave; thumbnails draw under two hundred.
  • The preview is sampled once per grid size and cached; a cross origin image without CORS keeps the neutral dots.
  • A loop runs only while its image generates and is on screen; finished, failed and stopped fields draw once and stop, and redraw on theme change.

Notes for AI

Give your coding assistant the Markdown reference instead of screenshots.

  • Wire onGenerate to your image API; it runs once per variation, so a provider that returns four images per call can be wrapped by caching one request and resolving each index from it.
  • Use request.variationOf to pass a seed or reference image for Vary. Return width and height so the footer and file names show the real size.
  • Keep model names to what your backend actually serves; the defaults are generic product names with provider marks.
  • Pair with a gallery or asset picker by listening to onSelect; the component holds only the current batch.

The full library index for assistants is at /llms.txt.