Image
Headless image component with state-driven placeholder and error fallback. Tracks the loading lifecycle and exposes retry(), with optional IntersectionObserver-driven lazy loading.
Usage
Image.Root owns the loading state machine via useImage. Image.Img renders the image element and reports load and error events to the context. Image.Placeholder is shown while idle or loading; Image.Fallback is shown on error.
Anatomy
<script setup lang="ts">
import { Image } from '@vuetify/v0'
</script>
<template>
<Image.Root>
<Image.Img />
<Image.Placeholder />
<Image.Fallback />
</Image.Root>
</template>src is the only native image attribute that doesn’t live on Image.Img because it drives the load state machine — the idle → loading → loaded | error lifecycle can’t function without knowing the URL to track, so Image.Root owns it. Every other image attribute (alt, width, height, srcset, sizes, crossorigin, referrerpolicy, decoding, loading, fetchpriority) is a pure rendering concern and lives on Image.Img, where it describes the element that actually renders it.
Architecture
Image.Root calls useImage to manage status, and optionally wires up useIntersectionObserver when the lazy prop is set. Children consume the root context via useImageRoot.
Lazy loading strategies
The component supports two distinct lazy loading mechanisms — pick the one that matches your requirements.
| Strategy | How | When to use |
|---|---|---|
| Native | loading="lazy" on Image.Img | Most below-the-fold images. Zero JS, browser-managed. |
| Observer | lazy prop on Image.Root | When you need precise control over when the source is set — e.g. blur-up transitions, prefetch, or custom intersection thresholds. |
You can combine both: use the observer for state control and loading="lazy" as a fallback for browsers without IntersectionObserver support.
Examples
Recipes
Hero image with high priority
Set loading="eager" and fetchpriority="high" on hero images to optimize LCP. Always include width and height to prevent layout shift.
<template>
<Image.Root src="/hero.jpg">
<Image.Img
alt="Hero"
width="1600"
height="900"
loading="eager"
fetchpriority="high"
/>
<Image.Placeholder>
<img src="/hero-tiny.jpg" class="w-full blur-sm" />
</Image.Placeholder>
</Image.Root>
</template>Retry on error
The retry() function is available from both Image.Root and Image.Fallback slot props.
<template>
<Image.Root src="/photo.jpg">
<Image.Img alt="Photo" />
<Image.Placeholder>Loading...</Image.Placeholder>
<Image.Fallback v-slot="{ retry }">
<button @click="retry">Retry</button>
</Image.Fallback>
</Image.Root>
</template>Styling
Every Image sub-component exposes data-state reflecting the current status (idle, loading, loaded, or error). Prefer styling against these data attributes with CSS over threading slot props — the transitions stay CSS-only and the template stays declarative.
<template>
<Image.Img
alt="Photo"
class="opacity-0 transition-opacity data-[state=loaded]:opacity-100"
/>
</template>/* Or with plain CSS */
[data-state='loaded'] { opacity: 1; }
[data-state='loading'] { animation: pulse 1s infinite; }
[data-state='error'] { border-color: red; }Slot props (status, isLoaded, etc.) remain available for the rare cases where logic has to branch in the template, but reach for CSS + data attributes first.
Accessibility
| Element | ARIA / behavior |
|---|---|
Image.Img | role="img", accepts alt for accessible name |
Image.Placeholder | aria-hidden="true" — placeholder is decorative |
Image.Fallback | role="img" — provide alternate text inside the slot |
Always pass width and height props on Image.Img to reserve layout space and prevent Cumulative Layout Shift while the image loads.
FAQ
Avatar is for identity / profile UIs with priority-based multi-source fallback (e.g. high-res then low-res then initials). Image is for general content images with state-driven placeholder and fallback for a single source.
Use native loading="lazy" for most cases — it’s declarative, zero-JS, and browser-managed. Use the lazy prop on Image.Root only when you need to control exactly when the source is set, such as for blur-up transitions, prefetching, or custom intersection thresholds.
Always set width and height on Image.Img. The browser uses these to reserve space before the image loads, eliminating Cumulative Layout Shift.
Yes — set renderless on both Image.Root and Image.Img, then compose them inside a native <picture> element. See the picture example.
Style against the data-state attribute that every Image sub-component exposes. Using data attributes keeps the transition CSS-only — no slot-prop threading required.
<template>
<Image.Img
alt="Photo"
class="opacity-0 transition-opacity duration-500 data-[state=loaded]:opacity-100"
/>
</template>The data-state attribute holds the current status (idle, loading, loaded, or error). The blur-up example uses this pattern.
src lives on Image.Root because it owns the state machine — the source is what the state is about. Image.Img accepts the rendering attributes (alt, width, height, srcset, sizes, etc.) that belong to the rendered element.