VImage
The main component. Combines lazy loading, placeholder, format switching, and transitions in one element.
vue
<VImage
src="/photo.jpg"
alt="Описание"
:width="1200"
:height="600"
blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
placeholder="data:image/jpeg;base64,..."
:widths="[400, 800, 1200]"
sizes="(max-width: 768px) 100vw, 50vw"
:lazy="true"
root-margin="300px"
fit="cover"
@load="onLoad"
@error="onError"
>
<template #error>
<div class="my-error">Image failed to load</div>
</template>
</VImage>Props
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | SrcSet | — | URL or object with format variants. Optional when image is given |
image | ImageMeta | — | Build-time metadata (a CLI manifest entry or ?vik import) — seeds src/width/height/blurhash/thumbhash/placeholder/sizes. Any explicit prop above overrides the matching field |
alt | string | — | Required. alt attribute on the <img>. In dev builds, a suspicious value (missing, whitespace-only, or filename-shaped like "photo.jpg") logs a console.warn — a deliberate alt="" for a decorative image never triggers it |
width | number | — | Intrinsic width; used to reserve aspect-ratio space |
height | number | — | Intrinsic height; used to reserve aspect-ratio space |
blurhash | string | — | BlurHash string; decoded to canvas in onMounted |
thumbhash | string | — | ThumbHash string; decoded to PNG data URL, used as blur-up placeholder |
placeholder | string | — | Base64 LQIP or ThumbHash data URL; overrides thumbhash if both provided |
placeholderMode | 'blur' | 'color' | 'shimmer' | 'blur' | 'color' shows a solid average color (from thumbhash); 'shimmer' shows an animated skeleton (no hash needed) |
placeholderColor | string | — | Explicit solid CSS color placeholder; takes precedence and needs no decode |
widths | number[] | — | Pixel widths for automatic width-based (w) srcset generation |
densities | number[] | Record<number, string> | — | Density descriptors (1x/2x/3x) for fixed-size images. List reuses src; map gives a distinct file per density. Takes precedence over widths, ignores sizes |
sizes | string | — | sizes attribute passed to <img> (width-based srcset only) |
breakpoints | BreakpointMap | — | Local breakpoints (merged with global plugin breakpoints) |
sources | ResponsiveSrc | — | Breakpoint-key → URL (or { avif?, webp?, fallback }) map for art direction, optionally combined with format switching per breakpoint |
lazy | boolean | true | Enable IntersectionObserver lazy loading |
rootMargin | string | "200px" | IO rootMargin — how far before the viewport loading starts |
threshold | number | 0 | IO threshold — intersection ratio required to trigger |
fit | ObjectFit | "cover" | CSS object-fit value on the <img> |
focal | FocalPoint | — | Focal point { x, y } (fractions 0–1) → object-position; keeps the subject in frame when fit="cover" crops |
maxRetries | number | 0 | Max retry attempts on load failure |
retryDelay | number | 1000 | Initial delay in ms; doubles each retry (exponential backoff) |
fetchpriority | 'high' | 'low' | 'auto' | — | Browser fetch priority hint |
decoding | 'async' | 'sync' | 'auto' | 'async' | Image decoding mode |
priority | boolean | false | Shorthand for the LCP/hero image — forces lazy=false, fetchpriority='high', decoding='sync'. Not automatic LCP detection (that isn't reliable pre-paint); mark the one image that matters instead of setting three props by hand |
respectSaveData | boolean | false | On a save-data connection: neutralizes priority (stays lazy) and downgrades src to the smallest URL available from densities/image.srcset. See Network-aware loading |
layout | 'fixed' | 'responsive' | 'fill' | — | Wrapper sizing preset. Unset keeps the current default (fills container width, aspect-ratio preserved). See Layout presets |
cdn | boolean | AutoLoaderConfig | — | Opt-in: routes a string src through autoLoader() — detects the CDN from the URL and rewrites it, no manual adapter wiring. See CDN adapters → Auto CDN detection with VImage |
loader | 'server' | — | Opt-in: routes a string src through the vue-image-kit/server on-demand handler via buildImageUrl(). See Self-hosted on-demand server → Wiring VImage to it |
loaderRoute | string | /_vik/image | Route override for loader="server" — takes precedence over the plugin/Nuxt-module serverRoute default |
Events
| Event | Payload | Description |
|---|---|---|
@load | Event | Fired when the image finishes loading |
@error | Event | Fired when the image fails to load |
Slots
| Slot | Description |
|---|---|
#error | Custom UI shown when the image fails to load. If omitted, a grey rectangle with a broken-image icon is shown. |
Examples
Simple image with lazy loading:
vue
<VImage src="/photo.jpg" alt="Landscape" />With blurhash and dimensions for aspect-ratio reservation:
vue
<VImage
src="/photo.jpg"
alt="Landscape"
:width="1200"
:height="800"
blurhash="LEHV6nWB2yk8pyo0adR*.7kCMdnj"
/>WebP/AVIF with srcset and blur-up:
vue
<VImage
:src="{ avif: '/photo.avif', webp: '/photo.webp', fallback: '/photo.jpg' }"
alt="Product"
:width="800"
:height="600"
placeholder="data:image/jpeg;base64,/9j/4AAQSkZJRgAB..."
:widths="[400, 800]"
sizes="(max-width: 640px) 100vw, 800px"
/>Disable lazy loading for above-the-fold images:
vue
<VImage src="/hero.jpg" alt="Hero" :lazy="false" />LCP/hero image — priority instead of three separate props:
vue
<VImage src="/hero.jpg" alt="Hero" :width="1600" :height="900" priority />From CLI/manifest output — no manual prop wiring:
vue
<script setup lang="ts">
import meta from './photo.jpg?vik' // or: import { images } from './assets/images'
</script>
<template>
<!-- src/width/height/blurhash/thumbhash/placeholder/sizes all come from meta -->
<VImage :image="meta" alt="Product photo" />
</template>Focal point — keep the subject in frame when cropping:
vue
<!-- With fit="cover" the image is cropped to the box; focal decides which
part survives. { x: 0.5, y: 0.3 } favours the upper-middle (e.g. a face). -->
<VImage
src="/portrait.jpg"
alt="Team member"
:width="400"
:height="400"
fit="cover"
:focal="{ x: 0.5, y: 0.3 }"
/>Cheapest placeholder — a solid average color (no canvas, 0 bytes):
vue
<!-- 'color' mode pulls the average RGBA straight from the ThumbHash header. -->
<VImage
src="/photo.jpg"
alt="Gallery item"
:width="600"
:height="400"
thumbhash="3OcRJYB4d3h/iIeHeEh3eIhw+j5n"
placeholder-mode="color"
/>
<!-- Or an explicit color you already know — needs no ThumbHash at all. -->
<VImage src="/photo.jpg" alt="Banner" placeholder-color="#1e3a8a" />Animated skeleton — when you have no hash at all:
vue
<!-- A CSS shimmer sweep until the image loads. Respects prefers-reduced-motion. -->
<VImage src="/photo.jpg" alt="Card" :width="400" :height="300" placeholder-mode="shimmer" />Custom error slot:
vue
<VImage src="/missing.jpg" alt="Missing">
<template #error>
<div class="placeholder">
<span>📷</span>
<p>Image unavailable</p>
</div>
</template>
</VImage>Handling events:
vue
<script setup lang="ts">
function onLoad(e: Event) {
console.log('Image loaded', e)
}
function onError(e: Event) {
console.warn('Image failed', e)
}
</script>
<template>
<VImage src="/photo.jpg" alt="Photo" @load="onLoad" @error="onError" />
</template>