Image

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.

Native <img> (no dependencies) components/image.tsx, media/types.tstheme · material · accent · density · font
Lounge of the Moda office with low sofas and tall windows

Workplace · 6 min read

How we redesigned the Moda office around quiet work

Installation

$ pnpm add @vamos-ui/react

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.

Dining room of Kalamış Bistro at lunch
1:1
Dining room of Kalamış Bistro at lunch
4:5
Dining room of Kalamış Bistro at lunch
4:3
Dining room of Kalamış Bistro at lunch
3:2
Dining room of Kalamış Bistro at lunch
16:9
Dining room of Kalamış Bistro at lunch
1.91:1

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)

Therapist giving a back massage with warm oilTherapist giving a back massage with warm oil

Focal point 72% · 58%

Therapist giving a back massage with warm oilTherapist giving a back massage with warm oil

Placeholders

blur sharpens an LQIP over 240 ms, color fills with the dominant colour, skeleton shimmers. A MediaAsset picks blur or color automatically.

Plated sea bass with fennel and lemon
blur — LQIP sharpens in
Plated sea bass with fennel and lemon
color — dominant colour
Plated sea bass with fennel and lemon
skeleton — shimmer
Plated sea bass with fennel and lemon
none — transparent

CDN loader

ImageLoaderProvider (or the media adapter's loader) builds the srcset. Same signature as the Next.js loader.

Two-storey villa with a pool in ÇeşmeVilla terrace at dusk

Next.js bridge

Hand next/image to VamosProvider once; every kit Image renders through it (fill inside the ratio box).

Product team planning the Q1 roadmap around a laptop

Rendered by next/image (fill mode inside the kit's ratio box).

Captioned figure

Figure + FigureCaption render <figure>/<figcaption> with an optional credit line.

Glass office tower seen from below against a clear sky
Levent Plaza, where the Istanbul team moves in March 2027.Photo: Can Yıldız

Error and processing

A broken source shows a quiet icon (or your fallback); assets with status "processing" get a badge.

Atlas Lojistik logo
Broken source
Selin Kaya
Custom fallback
Laptop on a deskProcessing
Still processing

API

Image

PropTypeDefault
src
A 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—
alt
Required 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 / height
Intrinsic size. Sets the box ratio when aspect is "auto" (no CLS).
number—
aspect
Box 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"
fit
object-fit inside the box.
"cover" | "contain""cover"
focal
0..1 → object-position. Passed to the loader as focal too.
{ x: number; y: number }asset.focalPoint
crop
Named crop from asset.crops, or a rect. Applied by the loader (non-destructive).
string | CropRect—
sizes
The sizes attribute, e.g. "(min-width: 1024px) 50vw, 100vw".
string"100vw" with a srcset
widths
srcset steps with a loader (capped at the asset width).
number[][320, 480, 640, 768, 1024, 1280, 1600, 1920]
quality
Passed to the loader.
number—
priority
Above the fold: loading="eager" + fetchpriority="high".
booleanfalse
placeholder
What 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 / placeholderCaption
Tune the art placeholder: seed (default alt), forced pattern, glyph, caption.
string / pattern / ReactNode | false / boolean | ReactNode—
blurDataURL
Tiny preview (LQIP data URL or small URL), blurred and scaled under the image.
stringasset.placeholder
color
Placeholder colour.
stringasset.dominantColor
loader
Overrides the provider loader for this image.
ImageLoader—
rounded
Corner radius from --radius.
"none" | "ui" | "ui-lg" | "full""none"
frame
A hairline --surface-border drawn above the image (logos on white, screenshots).
"none" | "surface""none"
fallback
Shown instead of the broken-image icon on error (initials, a logo mark).
ReactNode—
processing
Shows the Processing badge.
booleanasset.status === "processing"
imgClassName
Class for the <img>; className goes to the wrapper.
string—
labels
Override built-in strings (dictionary namespace image).
Partial<{ processing; error }>—

ImageLoaderProvider

PropTypeDefault
loader
({ src, width, quality, focal, crop, format }) => string. Used by every Image below.
ImageLoader—

Figure / FigureCaption

PropTypeDefault
gap
Figure: space between media and caption.
"sm" | "md""sm"
credit
FigureCaption: small uppercase credit after the caption.
ReactNode—

Helpers

PropTypeDefault
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—