Skip to main content
Vuetify0 is now in beta!
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

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

Anatomy
<script setup lang="ts">
  import { Image } from '@vuetify/v0'
</script>

<template>
  <Image.Root>
    <Image.Img />

    <Image.Placeholder />

    <Image.Fallback />
  </Image.Root>
</template>
Note

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.

StrategyHowWhen to use
Nativeloading="lazy" on Image.ImgMost below-the-fold images. Zero JS, browser-managed.
Observerlazy prop on Image.RootWhen 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.

vue
<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.

vue
<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.

vue
<template>
  <Image.Img
    alt="Photo"
    class="opacity-0 transition-opacity data-[state=loaded]:opacity-100"
  />
</template>
css
/* 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

ElementARIA / behavior
Image.Imgrole="img", accepts alt for accessible name
Image.Placeholderaria-hidden="true" — placeholder is decorative
Image.Fallbackrole="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

Was this page helpful?

Ctrl+/