Image reveal
ProAn image generation canvas where a soft, blurred preview made from the image's own colors sharpens top to bottom behind a soft-edged band. It works the way ChatGPT, Midjourney and Gemini show images forming, with fine film grain, a translucent progress pill and calm queued and error states.
Quiet landscapes at golden hour
- Showing an image model's progress on a canvas or in a chat reply.
- A grid of image variations that resolve one after another.
- A queued image job that should feel alive before it starts.
- Plain image loading from a CDN; use a blur-up placeholder or a skeleton.
- Text generation or tool calls; use text-shimmer or tool-call.
- Determinate file uploads; use progress.
Installation
Pro source and install commands unlock with a Pro plan.
import { ImageReveal } from "@/components/arc/image-reveal/image-reveal"; export function GeneratedImage({ job }: { job: { status: "queued" | "generating" | "done" | "error"; step: number; url?: string } }) { return ( <ImageReveal src={job.url} alt="A calm alpine lake at golden hour" status={job.status} progress={job.step / 30} steps={30} aspectRatio="4 / 5" onRetry={() => retry(job)} /> );}Estimated duration, no progress events
<ImageReveal src={url} status={url ? "done" : "generating"} duration={8000} />Staggered grid with a Generate again button
const [run, setRun] = useState(0); <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}> {urls.map((url, index) => ( <ImageReveal key={index} src={url} size="sm" seed={index + 1} runKey={run} loop={false} startDelay={index * 420} style={{ maxInlineSize: "none" }} /> ))}</div><Button onClick={() => setRun(run + 1)}>Generate again</Button>ImageReveal
Image generation the way current image models show it: a soft, heavily blurred preview sampled from the image's own colors with fine film grain, then a soft-edged band that sharpens the picture top to bottom while the rest gently comes into focus. With no props it plays a looping, simulated generation over sample photos.
srcstring–Final image URL. Cross-origin images work (pixels are never read back). Omit to cycle through sample photos.altstring"Generated image"Alt text for the final image, used as the canvas label once done.status"queued" | "generating" | "done" | "error"–Controlled state. Omit for auto mode, which runs queued, generating over `duration`, then done. Every change transitions smoothly.progressnumber–Controlled progress from 0 to 1 while generating. Coarse updates (one per model step) are smoothed into continuous motion. Omit to animate over `duration`.durationnumber5200Milliseconds one generation takes when `progress` is not controlled.stepsnumber30Steps shown in the pill, as in 18 / 30 (tabular numerals, reserved width).aspectRationumber | string1Width over height, as a number (1.5) or a CSS ratio ("4 / 5"). The image is cover-cropped to it.size"sm" | "md" | "lg""md"Overlay scale and corner radius. "sm" (grids, thumbnails) shows only the ring and 18 / 30; "md" and "lg" add the Generating label. The canvas always renders at device pixel ratio.colorstring–Any CSS color for the progress ring. Defaults to the accent.showProgressbooleantrueShows the translucent progress pill. It fades out shortly after the image resolves.sheenbooleantruePlays one soft light sheen across the image when it resolves. Skipped with reduced motion.queueLabelstring"Queued"Chip label while queued, such as "Queued, 2 ahead".errorMessagestring"Generation failed"Message in the error state.onRetry() => void–Shows a Retry button in the error state.onStatusChange(status: ImageRevealStatus) => void–Called when the displayed status changes, including auto mode transitions.onComplete() => void–Called once per run when the final image is fully resolved and crisp.autoPlaybooleantrueAuto mode only: starts generating on mount.loopbooleantrueAuto mode only: after `holdDuration` the image blurs back out and generates again.startDelaynumber0Auto mode only: extra queue time in milliseconds before a run, for staggered grids.queueDurationnumber900Auto mode only: milliseconds in the queue before each run.holdDurationnumber2600Auto mode only: milliseconds the finished image holds before a loop restarts.runKeystring | number–Auto mode only: change it to blur back out and run again (a Generate again button).seednumber1Seeds the film grain and the drift of the soft preview, so tiles in a grid differ.classNamestring–Class on the root. The root is 100% wide with a 560px max width; override max-inline-size in grids.styleCSSProperties–Inline styles on the root.- Tab
- Moves to the Retry button in the error state. The canvas itself is not focusable.
- EnterorSpace
- Activates Retry.
- The canvas has role img; its label names the state while generating and becomes the alt text once done.
- A polite live region announces Queued, Generating image, Image ready and the error message, not every step. Auto mode appends simulated.
- While generating, the pill is a progressbar with aria-valuenow and an aria-valuetext of Step n of total.
- Status is never color alone: the pill pairs a ring or check with a short label, and the error state pairs an alert icon with its message.
- Queued is the calm surface tone with one broad, soft shimmer that starts and ends fully off canvas, so its 2.6s loop has no seam.
- Generating fades in a soft preview built from the image itself (6 to 48 px wide copies, smoothly upscaled), crossfaded along that blur chain by progress for a gentle overall blur-to-sharp curve.
- A soft-edged band travels top to bottom: above it the photo is sharp, inside it a near-sharp copy blends in, below it the preview stays soft with fine monochrome grain (refreshed at 24fps) and a very slow drift that settles as it resolves.
- Progress is smoothed with a 200ms exponential follow, so step-wise model updates read as one continuous reveal; auto mode eases out so composition resolves early.
- One soft-light sheen sweeps across once at completion (1.1s), then the pill fades out.
- Every status change follows from the current pose: restarting, failing or swapping the photo blurs the sharp layers back out before anything resets underneath, and a photo swap crossfades from a snapshot, so there is never a hard cut.
- Pill labels crossfade (100ms out, 160ms in after a 60ms gap) and the pill width springs with no bounce.
- Reduced motion replaces the travelling band with a progress-driven crossfade and drops the drift, grain refresh, shimmer travel, spinner and sheen.
- The frame loop stops off screen (IntersectionObserver) and in background tabs, and draws nothing once the image is resolved.
- Fills its container width (up to 560px by default) and keeps the given aspect ratio.
- The canvas renders at device pixel ratio (capped at 2), so it stays crisp from a 120px thumbnail to a large canvas.
- Grain is sized in device pixels, so it stays fine and crisp at every size.
- Canvas 2D only, no WebGL and no per-pixel work per frame: a few drawImage calls, one masked band strip and a pattern fill.
- The blur chain is built once per image and size; the fully sharp region is a direct copy, only the band strip goes through a gradient mask.
- Frames stop drawing when the image is resolved and nothing changes, and the loop stops entirely off screen.
- Images are decoded off the main thread before use.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Use for image generation surfaces: an image canvas, an inline image in a chat reply, a variations grid, or an empty state while a first image renders.
- Wire a real job by passing status and progress (step / total). If the backend has no progress events, pass status only and an estimated duration.
- Use size="sm" in grids, give each tile a different seed, and override max-inline-size through className or style.
- The preview is sampled from the final image, so pass src as soon as a preview or final URL exists; until it loads the tile stays in the calm queued look.
- For text or agent work in progress, pick a text shimmer or an agent status component instead.
The full library index for assistants is at /llms.txt.