Responsive Images
srcset + sizes
Pass widths to auto-generate the srcset attribute:
<VImage
src="/photo.jpg"
alt="Photo"
:widths="[400, 800, 1200]"
sizes="(max-width: 768px) 100vw, 50vw"
/>Renders:
<img
src="/photo.jpg"
srcset="/photo.jpg 400w, /photo.jpg 800w, /photo.jpg 1200w"
sizes="(max-width: 768px) 100vw, 50vw"
alt="Photo"
/>When widths is not provided, srcset is not added — the plain src is used. When widths is provided but sizes is not, sizes defaults to "100vw".
Density descriptors (1x / 2x / 3x)
For fixed-size images — icons, avatars, logos — use densities instead of widths. The browser picks the candidate matching the device pixel ratio; no sizes is needed. densities takes precedence over widths (the two descriptor types can't be mixed in one srcset).
:densities accepts two forms:
<!-- 1. Per-density URL map — distinct files (recommended for static assets). -->
<VImage
src="/avatar.png"
alt="Avatar"
:width="48"
:height="48"
:densities="{ 1: '/avatar.png', 2: '/avatar@2x.png', 3: '/avatar@3x.png' }"
/>
<!-- → srcset="/avatar.png 1x, /avatar@2x.png 2x, /avatar@3x.png 3x" -->
<!-- 2. Density list — reuses the single `src` for every density. Only useful
when the URL itself is resolution-aware (a CDN/DPR endpoint). -->
<VImage src="https://cdn.example.com/avatar?dpr=auto" alt="Avatar" :densities="[1, 2, 3]" />
<!-- → srcset="…?dpr=auto 1x, …?dpr=auto 2x, …?dpr=auto 3x" -->Using the utilities directly:
import { generateSrcset, generateSizes, generateDensitySrcset } from 'vue-image-kit'
generateSrcset('/photo.jpg', [400, 800, 1200])
// → '/photo.jpg 400w, /photo.jpg 800w, /photo.jpg 1200w'
generateSizes('(max-width: 768px) 100vw, 50vw')
// → '(max-width: 768px) 100vw, 50vw'
generateSizes()
// → '100vw'
generateDensitySrcset('/logo.png', [1, 2, 3])
// → '/logo.png 1x, /logo.png 2x, /logo.png 3x'
// Distinct files per density via a URL map:
generateDensitySrcset({ 1: '/a.png', 2: '/a@2x.png' }, [1, 2])
// → '/a.png 1x, /a@2x.png 2x'WebP / AVIF source switching
When src is an object instead of a string, <VImage> renders a <picture> element with the appropriate <source> elements:
<VImage
:src="{
avif: '/photo.avif',
webp: '/photo.webp',
fallback: '/photo.jpg',
}"
alt="Photo"
:width="1200"
:height="800"
/>Renders:
<picture>
<source srcset="/photo.avif" type="image/avif" />
<source srcset="/photo.webp" type="image/webp" />
<img src="/photo.jpg" alt="Photo" width="1200" height="800" />
</picture>The browser picks the first format it supports. If only webp is provided, only one <source> is added. fallback is always required.
SrcSet object
interface SrcSet {
avif?: string // URL of the AVIF version
webp?: string // URL of the WebP version
fallback: string // Required — the original format (JPEG/PNG)
}Responsive sources — art direction
Use this when you need to serve a fundamentally different image (different crop, different composition) based on screen size. Implemented via named breakpoints — the browser picks the first matching <source media="...">.
Global breakpoints (set once when installing the plugin)
// main.ts
app.use(VImageKitPlugin, {
breakpoints: {
sm: '(max-width: 640px)',
md: '(max-width: 1024px)',
lg: '(min-width: 1025px)',
},
})Using in components — keys only
<VImage
src="/hero-desktop.jpg"
alt="Hero"
:sources="{
sm: '/hero-mobile.jpg',
md: '/hero-tablet.jpg',
}"
/>Generates:
<picture>
<source media="(max-width: 640px)" srcset="/hero-mobile.jpg" />
<source media="(max-width: 1024px)" srcset="/hero-tablet.jpg" />
<img src="/hero-desktop.jpg" alt="Hero" />
</picture><source> order is set automatically in ascending max-width order — required by <picture>, which picks the first matching source.
Per-component breakpoints
Merged with global breakpoints. Local keys take priority on conflict:
<VImage
src="/product-desktop.jpg"
alt="Product"
:breakpoints="{
xs: '(max-width: 375px)',
wide: '(min-width: 1600px)',
}"
:sources="{
xs: '/product-xs.jpg',
sm: '/product-mobile.jpg',
md: '/product-tablet.jpg',
wide: '/product-wide.jpg',
}"
/>The resulting <picture> contains <source> elements for xs, sm, md (from merged breakpoints), and wide — sorted automatically.
Combining with AVIF/WebP
Responsive sources (sources) and format sources (src as object) are independent and rendered together:
<VImage
:src="{ avif: '/hero.avif', webp: '/hero.webp', fallback: '/hero.jpg' }"
:sources="{ sm: '/hero-mobile.jpg' }"
alt="Hero"
/><picture>
<source media="(max-width: 640px)" srcset="/hero-mobile.jpg" />
<source srcset="/hero.avif" type="image/avif" />
<source srcset="/hero.webp" type="image/webp" />
<img src="/hero.jpg" alt="Hero" />
</picture>That covers "one crop set + one format set, independent of each other." For a different crop and different formats per breakpoint — e.g. a portrait AVIF/WebP crop on mobile, a landscape AVIF/WebP crop on desktop — a breakpoint's value in sources can itself be a { avif?, webp?, fallback } object instead of a plain URL:
<VImage
alt="Hero"
:sources="{
sm: { avif: '/hero-mobile.avif', webp: '/hero-mobile.webp', fallback: '/hero-mobile.jpg' },
md: { webp: '/hero-tablet.webp', fallback: '/hero-tablet.jpg' },
}"
src="/hero-desktop.jpg"
/><picture>
<source media="(max-width: 640px)" srcset="/hero-mobile.avif" type="image/avif" />
<source media="(max-width: 640px)" srcset="/hero-mobile.webp" type="image/webp" />
<source media="(max-width: 640px)" srcset="/hero-mobile.jpg" />
<source media="(max-width: 1024px)" srcset="/hero-tablet.webp" type="image/webp" />
<source media="(max-width: 1024px)" srcset="/hero-tablet.jpg" />
<img src="/hero-desktop.jpg" alt="Hero" />
</picture>Plain-URL and format-object breakpoints can be mixed freely in the same sources object. avif/webp are both optional per breakpoint — only the formats you actually have are emitted.
BreakpointMap
type BreakpointMap = Record<string, string>
// key — arbitrary name, value — CSS media queryBreakpoint priority
| Source | Priority |
|---|---|
Local breakpoints prop on the component | High — overrides global keys on conflict |
Global breakpoints from VImageKitPlugin | Base — available in all components |
buildSizes helper
Build a sizes attribute string from a breakpoint-keyed object — works with the plugin's named breakpoints.
import { buildSizes } from 'vue-image-kit'
const breakpoints = { sm: '(max-width: 640px)', md: '(max-width: 1024px)' }
buildSizes({ sm: '100vw', md: '50vw', default: '33vw' }, breakpoints)
// → '(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw'generatePreloadLink
Generate a <link rel="preload"> HTML string for critical above-the-fold images. Use in Nuxt's useHead or inject into SSR <head> to improve LCP.
import { generatePreloadLink, generateSrcset } from 'vue-image-kit'
const srcset = generateSrcset('/hero.jpg', [400, 800, 1200])
const link = generatePreloadLink('/hero.jpg', {
srcset,
sizes: '100vw',
})
// → '<link rel="preload" as="image" href="/hero.jpg" imagesrcset="..." imagesizes="100vw">'In Nuxt:
<script setup lang="ts">
import { generatePreloadLink } from 'vue-image-kit'
useHead({
link: [{ innerHTML: generatePreloadLink('/hero.jpg', { sizes: '100vw' }) }],
})
</script>Layout presets
The layout prop switches how the wrapper is sized. Leaving it unset keeps the current default — fills the container width, aspect-ratio preserved from width/height — so nothing changes for existing usage.
fixed — an exact width×height box, no responsive scaling (like a plain <img width height>):
<VImage src="/icon.jpg" alt="Icon" :width="64" :height="64" layout="fixed" />responsive — same container-filling behavior as the default, plus an auto-generated sizes from width when sizes isn't given explicitly ((min-width: {width}px) {width}px, 100vw — "as wide as its intrinsic size, otherwise the full viewport width"):
<VImage
src="/photo.jpg"
alt="Photo"
:width="800"
:height="600"
:widths="[400, 800, 1200]"
layout="responsive"
/>fill — absolutely fills a positioned parent (position: absolute; inset: 0); the parent needs position: relative (or similar) itself. width/height become optional — common for hero banners or cards where the container defines the box:
<div style="position: relative; aspect-ratio: 16 / 9;">
<VImage src="/hero.jpg" alt="Hero" layout="fill" fit="cover" priority />
</div>