A picture that never pops in: its box breathes while it loads, it fades in, and a new picture crossfades over the old one with a spinner meanwhile.
$ pnpm dlx shadcn@latest add @dashral/imageInstallation
$ pnpm dlx shadcn@latest add @dashral/imageAdds the component, the components it builds on, its npm packages and the theme tokens it uses. Or by its full URL: https://dashral.com/r/image.json
When to use
- A picture that loads from the network: a product photo, a cover, an article's lead, a thumbnail in a list.
- A picture that changes while it's on screen: a product's views, a variant, a chosen file.
When not to use
- A person or a team: Avatar, which falls back to initials.
- Pictures to open large and step through: Lightbox.
- A small icon that ships with the page: an img, or an SVG.
Usage
Give it a shape with ratio; it breathes while it loads, then fades in.
import { Image } from "@/components/dashral/image"
export default function Cover() {
return <Image src="https://picsum.photos/id/1018/1280/720" alt="Mountains above a lake" ratio={16 / 9} className="rounded-xl" />
}Using the npm package? Import from @dashral/ui/components/image instead, here and in the examples; the rest is the same.
Examples
Changing the picture
A new src answers at once: a spinner over the picture still showing, then the new one fades in over it.
In a box you size
fill: the picture fills a box sized with className, such as a small square in a list.
Its own shape
Without ratio the picture keeps its own shape; width and height hold its place until it loads.
API reference
Image
Also takes every prop of <img>(opens in a new tab).
| Prop | Type | Default |
|---|---|---|
src | stringThe picture. Another src crossfades to it, with a spinner while it loads. Required. | — |
alt | stringDescribes the picture; "" for one that's only decoration. Required. | — |
ratio | numberA fixed shape, width over height (16 / 9, 1): the box keeps it and the picture fills it. | — |
fill | booleanThe picture fills a box you size yourself with className. | false |
fit | "cover" | "contain"In a fixed box: cover fills it and crops, contain shows the whole picture. | "cover" |
width / height | numberWithout ratio or fill, they hold the picture's place until it loads. | — |
fallback | ReactNodeShown when the picture can't be loaded. | a broken-picture mark |
imageAs | ElementTypeYour image component, e.g. Next.js Image. With ratio or fill it gets Next.js Image's fill (no width and height needed); for its own shape, pass width and height. | "img" |
onLoad / onError | (event) => voidCalled for the picture asked for, as on an img. | — |
classNames | { image }The img's classes; className is the box's (its size and corners). | — |
className | stringExtra classes, merged with cn() so yours win. | — |
Accessibility
- alt names the picture as on an img; while a new one loads, the spinner is announced as Loading.
- A picture that can't be loaded keeps its alt, as the box's name.
- With reduced motion nothing breathes or fades: the picture is simply there once it has loaded.

