Image generation
FreeA card that shows an image as it is generated, from queued to complete.
pnpm dlx shadcn@latest add https://caliperui.site/r/image-generation.jsonA quiet harbour at dusk, flat shapes, muted teal and clay
Image ready
When to use #
Use it for
- An assistant produces images and the wait should feel alive.
- You need retry and download actions on the result.
Skip it when
- The image already exists and only needs viewing; use the image viewer.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/image-generation.json
Adds its 4 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
import { Download } from "lucide-react" import { useRef, useState } from "react" import { Button } from "@/components/ui/button" import { ImageGeneration, type GenerationStatus } from "@/components/ui/image-generation" const stages = ["Composing the scene", "Adding detail", "Refining edges"]; const covers = ["/media/harbour.svg", "/media/terrace.svg", "/media/ridge.svg"]; export function ImageGenerationDemo() { const [status, setStatus] = useState<GenerationStatus>("done"); const [progress, setProgress] = useState(1); const [cover, setCover] = useState(0); const [fail] = useState(false); const timers = useRef<ReturnType<typeof setTimeout>[]>([]); const ticker = useRef<ReturnType<typeof setInterval>>(undefined); const later = (ms: number, fn: () => void) => timers.current.push(setTimeout(fn, ms)); const generate = () => { timers.current.forEach(clearTimeout); clearInterval(ticker.current); setStatus("queued"); setProgress(0); later(900, () => { setStatus("generating"); let p = 0; ticker.current = setInterval(() => { p = Math.min(1, p + 0.045); setProgress(p); if (fail && p > 0.6) { clearInterval(ticker.current); setStatus("failed"); } else if (p >= 1) { clearInterval(ticker.current); setCover((c) => (c + 1) % covers.length); setStatus("done"); } }, 160); }); }; const cancel = () => { timers.current.forEach(clearTimeout); clearInterval(ticker.current); setStatus("canceled"); }; return ( <ImageGeneration prompt="A quiet harbour at dusk, flat shapes, muted teal and clay" alt="Illustration of a sailboat in a quiet harbour" status={status} src={covers[cover]} progress={status === "queued" ? undefined : progress} stage={stages[Math.min(2, Math.floor(progress * 3))]} onCancel={cancel} onRetry={generate} actions={ <> <Button variant="secondary" size="sm" onClick={generate}>Vary</Button> <Button variant="ghost" size="sm" aria-label="Download image"><Download aria-hidden="true" /> Download</Button> </> } /> ); }
API reference #
ImageGeneration
component
Accessibility #
It exposes progressbar, alert and status semantics, with aria-busy, aria-hidden, aria-label, aria-valuemin, aria-valuemax and aria-valuenow kept in step with state. Changes that matter are announced through a live region.
No custom key handling; Tab and the native activation keys do the work.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.