Default (skeleton → image, lazy-loaded)
Custom loading slot
Loading…
Error fallback (invalid src)
Grid (lazy load as you scroll)
"use client"
import { AsyncImage } from "@/components/ui/preskok-ui/async-image"
export function AsyncImageDemo() {
return (
<div className="space-y-8">
<div className="space-y-2">
<p className="text-muted-foreground text-sm font-medium">
Default (skeleton → image, lazy-loaded)
</p>
<div className="flex gap-4">
<AsyncImage.Root
src="https://picsum.photos/200/200"
alt="Placeholder"
width={200}
height={200}
/>
<AsyncImage.Root
src="https://picsum.photos/201/201"
alt="Placeholder"
width={200}
height={200}
/>
</div>
</div>
<div className="space-y-2">
<p className="text-muted-foreground text-sm font-medium">
Custom loading slot
</p>
<AsyncImage.Root
src="https://picsum.photos/300/150"
alt="Custom loading"
width={300}
height={150}
>
<AsyncImage.Loading className="bg-muted/50 flex items-center justify-center">
<span className="text-muted-foreground text-xs">Loading…</span>
</AsyncImage.Loading>
</AsyncImage.Root>
</div>
<div className="space-y-2">
<p className="text-muted-foreground text-sm font-medium">
Error fallback (invalid src)
</p>
<AsyncImage.Root
src="https://invalid.example/not-found.jpg"
alt="Will error"
width={200}
height={200}
/>
</div>
<div className="space-y-2">
<p className="text-muted-foreground text-sm font-medium">
Grid (lazy load as you scroll)
</p>
<div className="grid grid-cols-3 gap-3">
{Array.from({ length: 6 }).map((_, i) => (
<AsyncImage.Root
key={i}
src={`https://picsum.photos/160/120?random=${i + 1}`}
alt={`Image ${i + 1}`}
width={160}
height={120}
/>
))}
</div>
</div>
</div>
)
}
Installation
CLI
pnpmnpmyarnbunpnpm dlx @preskok-org/ui@latest add async-image
Usage
Basic usage with default loading skeleton and error fallback:
import { AsyncImage } from "@/registry/preskok/ui/preskok-ui/async-image"
export function AsyncImageExample() {
return (
<AsyncImage.Root
src="https://example.com/image.jpg"
alt="Description"
width={300}
height={200}
/>
)
}With custom slots:
<AsyncImage.Root
src="/photo.jpg"
alt="Photo"
width={400}
height={300}
>
<AsyncImage.Loading className="flex items-center justify-center bg-muted">
<span>Loading…</span>
</AsyncImage.Loading>
<AsyncImage.Error>Failed to load image</AsyncImage.Error>
</AsyncImage.Root>Disable lazy loading (load immediately):
<AsyncImage.Root
src="/hero.jpg"
alt="Hero"
width={800}
height={400}
lazyLoad={false}
/>Props
AsyncImage.Root
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | — | Image URL. Empty string triggers error state. |
alt | string | "" | Accessible alt text for the image. |
width | number | string | — | Width of the container and image. |
height | number | string | — | Height of the container and image. |
imgProps | Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "alt" | "width" | "height"> | — | Props passed to the underlying <img> when loaded. |
loadingDelayMs | number | 0 | Optional delay in ms before transitioning from loading to loaded (e.g. to avoid flicker). |
lazyLoad | false | IntersectionObserverInit | { threshold: 0.01, rootMargin: "75%" } | Lazy load when in view. Set to false to load immediately. |
onLoadingEnd | () => void | — | Called when the image has loaded successfully. |
onErrorFallback | () => void | — | Called when the image fails to load. |
children | React.ReactNode | — | Optional compound slots: AsyncImage.Loading, AsyncImage.Error, AsyncImage.Content. |
All other div props (e.g. className, style) are forwarded to the root container.
Slots
- AsyncImage.Loading — Rendered while
status === "loading". Default: skeleton placeholder. - AsyncImage.Error — Rendered when
status === "error". Default: icon + "Image unavailable". - AsyncImage.Content — Rendered when
status === "loaded". Default: the<img>element. Override to customize the loaded image element.