Image generation
ProAn image generation card where four images resolve as a pixel mosaic, from coarse colour blocks to full detail under a soft shimmer, each with its own progress ring. Pick one to upscale with a sweeping pixel band, vary or download it, with real provider marks and a built-in simulation.
Four images appear here
- 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.
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 generation card: prompt, model and aspect chips, and a 2×2 grid where each image resolves as a pixel mosaic, from coarse colour blocks to full detail, under its own progress ring. Pick one to upscale (it expands to fill the grid), vary or download. With no props it runs a local simulation on sample photos.
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 and drive the grid's shape.aspectRatiostring–Controlled aspect id. Pair with onAspectRatioChange.defaultAspectRatiostringfirst ratioStarting aspect id when uncontrolled.onAspectRatioChange(id: string) => void–Fires when an aspect chip is chosen. The new shape applies to the next generation; the current grid keeps its own.onGenerate(request: ImageGenerationRequest, context: { signal: AbortSignal; onProgress(progress: number, preview?: string): void }) => Promise<ImageGenerationImage>simulateImageGenerationProduces one image. Called once per tile, in parallel, for Generate, Vary (request.variationOf is set) and Retry. Report progress from 0 to 1, optionally with a preview URL that the pixel mosaic resolves from (without one, a quiet placeholder grid shimmers until the image arrives and fades in); without reports the ring estimates. Reject to mark that tile failed; the signal aborts on Stop.onUpscale(image: ImageGenerationImage, context: ImageGenerationContext) => Promise<ImageGenerationImage>simulateImageUpscaleUpscales the chosen image while it is expanded. Return the new image with its width and height.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 when the person picks or clears an image, and with the upscaled image once an upscale finishes.suggestionsstring[]three sample promptsPrompts offered in the empty grid before the first generation.autoGeneratebooleantrue with the simulation, false with onGenerateGenerates once when the card first scrolls into view, then picks image 1 if nobody has touched the card.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 and reports frame by frame progress over about two to three seconds. Nothing is sent anywhere.
No props.
- Cmd/Ctrl+Enter
- Generate from the prompt field.
- Arrow keys
- Move between model chips or aspect chips (radio groups), and between images in the grid.
- EnterorSpace
- Pick or clear the focused image.
- Escape
- Close the expanded image and return to the grid.
- Tab
- Prompt, model, aspect, Generate, the selected image, then Upscale, Vary and Download.
- Model and aspect chips are radio groups with a roving tab stop; collapsed model chips keep their name as the accessible label and in a tooltip.
- Each finished image is a toggle button with aria-pressed and the image description; running, failed and stopped tiles announce their state.
- A polite live region reports starts, finishes, failures, upscales and downloads, and says when results are simulated.
- Status is never color alone: failures pair a danger icon with "Failed", stops a square icon with "Stopped", selection an accent ring with a check badge.
- While an image is expanded the grid behind it is inert, so focus cannot land on hidden tiles.
- Each tile is a pixel mosaic on a canvas: coarse 4 column colour blocks fade in from the centre in a seeded order, then each step doubles the grid (8, 16, 32, 64 columns) with finer blocks landing exactly on coarser ones, and the last step crossfades to the full image. One progress motion value per tile drives the mosaic, the ring and the percentage, so nothing re-renders per frame.
- While a tile runs, a soft light band sweeps diagonally across the unresolved blocks, quantised to the resolving grid; it fades out as detail arrives and its loop pauses off screen.
- The real <img> takes over on top of an identical final frame, so the hand off never flashes. Done tiles zoom 2% on hover and press to 98.5%.
- Upscale sweeps a narrow band of crisp pixels left to right across the expanded image: soft ahead of it, sharp behind it, all masked from one motion value.
- Button labels that change (Generate and Stop, Upscale, Upscaling, Upscaled, Download and Saved) are stacked in one cell and crossfade, so no button changes width.
- Upscale grows the chosen tile from its cell to the full grid with a bounce-free 240ms spring on position and size; the other tiles fade out. Closing reverses it.
- The grid springs to a new aspect ratio; a container resize cuts straight to the new size so nothing wobbles.
- Footer status lines crossfade (100ms out, 160ms in, 2px rise); the chosen model chip springs its name open by measured width.
- Reduced motion keeps the progress ring, the stepped mosaic (driven by progress, not time) and final states, and drops the shimmer loop, the upscale sweep, hover zoom and the expand travel.
- The card fills its container up to 640px. Chips and Generate wrap into two rows below about 420px.
- The grid keeps the batch's aspect ratio and caps its height at 420px, so portrait shapes stay compact on desktop.
- Below 520px of card width the footer stacks: status on top, three equal action buttons below.
- Progress is written straight to motion values and the DOM; React re-renders only on state changes (preview arrives, tile finishes).
- Mosaic levels are built once per image and tile size by halving the previous level, so every block is a true average; a frame is one scaled draw plus at most about 1,300 block draws per tile.
- The canvas loop runs only while a tile is generating and on screen; settled tiles draw once and stop.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Wire onGenerate to your image API; it runs once per tile, 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.