Image
An img that reserves its space, covers its own load with a skeleton, shimmer or blurred thumbnail, builds a srcset from a list of widths, and renders a fallback in the same box when the URL is dead.
src/components/ui/image.tsxAbout
From the component's own source, where it was written.
An <img> that reserves its space, covers its own load, and survives a 404.
Three things a bare <img> gets wrong on a real page:
**Layout shift.** An image with no intrinsic size is zero pixels tall until it decodes, then shoves everything below it down the page. ratio puts the box on the page immediately, so the reflow never happens.
**The blank frame.** Between "requested" and "decoded" there is nothing to look at. mask fills that gap — a skeleton, a shimmer, or a blurred thumbnail that dissolves into the real thing. That last one matters for generated images in particular: they arrive slowly and often *after* their own low-resolution preview, so the preview is genuinely useful rather than decorative.
**The cached-image flash.** This is the subtle one and the reason for the layout effect below. A cached image can finish decoding before React attaches onLoad, so the event never fires and the placeholder stays up forever — or, if you reset on every render, flashes for a frame on an image that was already there. Reading complete before paint settles both.
Failure is a state, not a broken icon: fallback renders in the same box, so a dead URL costs the layout nothing.
Install
npx astralyx-ui add image
Usage
import { Image } from '@/components/ui/image'
<Image
src="/hero.jpg"
alt="The console, mid-deploy"
ratio={16 / 9}
mask="blur"
blurSrc={lqip}
widths={[480, 960, 1440]}
sizes="(min-width: 1024px) 640px, 100vw"
/>Composer
Change the props and the source below follows.
<Image
src="/hero.jpg"
alt="A workstation at dusk"
ratio={16 / 9}
mask="blur"
fit="cover"
blurSrc={lqip}
/>Examples
Masks, side by side
<Image src={src} alt="…" ratio={4 / 3} mask="skeleton" />
<Image src={src} alt="…" ratio={4 / 3} mask="shimmer" />
<Image src={src} alt="…" ratio={4 / 3} mask="blur" blurSrc={lqip} />When the URL is dead
<Image src="/gone.jpg" alt="Missing" ratio={16 / 9} errorLabel="Could not load" />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
Image
Extends Omit< ComponentProps<'img'>, 'src' | 'srcSet' | 'sizes' | 'placeholder' | 'width' | 'height' >.
| Prop | Type | Default | Description |
|---|---|---|---|
| alt | string | required | alt is required, deliberately. An image with no alt is either meaningful and unreadable, or decorative and should say so — pass alt="" for the second, which is a decision rather than an omission. |
| src | string | required | alt is required, deliberately. An image with no alt is either meaningful and unreadable, or decorative and should say so — pass alt="" for the second, which is a decision rather than an omission. |
| mask | 'skeleton' | 'shimmer' | 'blur' | 'none' | 'skeleton' | What fills the frame until the image decodes. blur needs blurSrc and degrades to skeleton without it. |
| blurSrc | string | — | A tiny inline stand-in, blurred and scaled up under the real image. Worth it for generated images, which arrive slowly and often after their own preview. |
| widths | number[] | — | Builds a srcset. The browser picks using sizes, the viewport and the device pixel ratio — facts this component does not have. srcFor defaults to a ?w= parameter. |
| srcFor | (src: string, width: number) => string | defaultSrcFor | Builds a srcset. The browser picks using sizes, the viewport and the device pixel ratio — facts this component does not have. srcFor defaults to a ?w= parameter. |
| sizes | string | — | Builds a srcset. The browser picks using sizes, the viewport and the device pixel ratio — facts this component does not have. srcFor defaults to a ?w= parameter. |
| ratio | number | — | Width over height. Puts the box on the page immediately, so a decoding image never shoves the rest of the page down. |
| fit | 'cover' | 'contain' | 'fill' | 'none' | 'scale-down' | 'cover' | — |
| fallback | ReactNode | — | Rendered in the same box on failure, so a dead URL costs the layout nothing. |
| errorLabel | string | 'Image unavailable' | Rendered in the same box on failure, so a dead URL costs the layout nothing. |
| priority | boolean | false | eager + fetchPriority="high", for the one image above the fold worth blocking on. Everything else stays lazy. |
| onStatusChange | (status: ImageStatus) => void | — | For a gallery that wants to count what has landed. |
Types
| Type | Definition | Description |
|---|---|---|
ImageStatus | 'loading' | 'loaded' | 'error' | An <img> that reserves its space, covers its own load, and survives a 404. |