One image primitive for panels and websites: no layout shift (width/height + aspect-ratio), focal point to object-position, a blur / colour / skeleton placeholder that cross-fades out, srcset from a CDN loader or the asset's variants. Framework-agnostic: a plain <img>, or your framework's image through VamosProvider.
Workplace · 6 min read
How we redesigned the Moda office around quiet work
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { Image } from "@vamos-ui/react";
<Image src={asset} aspect="16/9" sizes="(min-width: 1024px) 50vw, 100vw" rounded="ui" />
<Image src="/team/elif.jpg" alt="Elif Demir" width={800} height={800} aspect="1/1" rounded="full" />The kit never imports next/image. Pass it to VamosProvider (or use createVamosNextProvider from @vamos-ui/react/next).
Formats (AVIF/WebP) are the loader's job (format: "auto"); art-direction <picture> sources are planned.
Glass mode never blurs over the image; only the frame is drawn above it.
Examples
Aspect ratios
The box ratio comes from aspect, not from a size prop; the width follows the container.
Focal point
focal (0..1) becomes object-position, so every crop keeps the subject in frame. It is image space and is not mirrored in RTL.
Centered (default)
Focal point 72% · 58%
Placeholders
blur sharpens an LQIP over 240 ms, color fills with the dominant colour, skeleton shimmers. A MediaAsset picks blur or color automatically.
CDN loader
ImageLoaderProvider (or the media adapter's loader) builds the srcset. Same signature as the Next.js loader.
Next.js bridge
Hand next/image to VamosProvider once; every kit Image renders through it (fill inside the ratio box).
Rendered by next/image (fill mode inside the kit's ratio box).
Captioned figure
Figure + FigureCaption render <figure>/<figcaption> with an optional credit line.
Error and processing
A broken source shows a quiet icon (or your fallback); assets with status "processing" get a badge.


API
Image
| Prop | Type | Default |
|---|---|---|
srcA URL, or an asset that supplies alt, width/height, focal point, placeholder, variants and crops. null / "" renders seeded placeholder art (4:3 unless aspect is set). | string | MediaAsset | null | — |
altRequired with a URL; optional with an asset (overrides asset.alt). "" marks the image decorative (role="presentation"). Dev warning when it looks like a file name. | string | — |
width / heightIntrinsic size. Sets the box ratio when aspect is "auto" (no CLS). | number | — |
aspectBox ratio. auto uses width/height; without either the image flows at its natural height. | "auto" | "1/1" | "4/3" | "3/2" | "16/9" | "21/9" | "4/5" | "1.91/1" | number | "auto" |
fitobject-fit inside the box. | "cover" | "contain" | "cover" |
focal0..1 → object-position. Passed to the loader as focal too. | { x: number; y: number } | asset.focalPoint |
cropNamed crop from asset.crops, or a rect. Applied by the loader (non-destructive). | string | CropRect | — |
sizesThe sizes attribute, e.g. "(min-width: 1024px) 50vw, 100vw". | string | "100vw" with a srcset |
widthssrcset steps with a loader (capped at the asset width). | number[] | [320, 480, 640, 768, 1024, 1280, 1600, 1920] |
qualityPassed to the loader. | number | — |
priorityAbove the fold: loading="eager" + fetchpriority="high". | boolean | false |
placeholderWhat shows while loading. Default picks blur with a blurDataURL, color with a colour, else skeleton. art: seeded pattern while loading and on error; always used when there is no src (see Placeholder Art). | "blur" | "color" | "skeleton" | "art" | "none" | blur → color → skeleton |
seed / placeholderPattern / placeholderIcon / placeholderCaptionTune the art placeholder: seed (default alt), forced pattern, glyph, caption. | string / pattern / ReactNode | false / boolean | ReactNode | — |
blurDataURLTiny preview (LQIP data URL or small URL), blurred and scaled under the image. | string | asset.placeholder |
colorPlaceholder colour. | string | asset.dominantColor |
loaderOverrides the provider loader for this image. | ImageLoader | — |
roundedCorner radius from --radius. | "none" | "ui" | "ui-lg" | "full" | "none" |
frameA hairline --surface-border drawn above the image (logos on white, screenshots). | "none" | "surface" | "none" |
fallbackShown instead of the broken-image icon on error (initials, a logo mark). | ReactNode | — |
processingShows the Processing badge. | boolean | asset.status === "processing" |
imgClassNameClass for the <img>; className goes to the wrapper. | string | — |
labelsOverride built-in strings (dictionary namespace image). | Partial<{ processing; error }> | — |
ImageLoaderProvider
| Prop | Type | Default |
|---|---|---|
loader({ src, width, quality, focal, crop, format }) => string. Used by every Image below. | ImageLoader | — |
Figure / FigureCaption
| Prop | Type | Default |
|---|---|---|
gapFigure: space between media and caption. | "sm" | "md" | "sm" |
creditFigureCaption: small uppercase credit after the caption. | ReactNode | — |
Helpers
| Prop | Type | Default |
|---|---|---|
getImageSources(src, options)Pure (server-safe) src/srcset builder. useImageSources() is the hook version with the provider loader. | → { src, srcSet?, sizes? } | — |
aspectRatioOf(aspect, w?, h?)"16/9" → 1.777… | → number | undefined | — |
focalToObjectPosition(focal){ x: 0.62, y: 0.4 } → "62% 40%". | → string | — |