Skip to main content
Vuetify0 v1.0 releases July 22, 2026
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

useImage

Tracks image loading state as a reactive state machine with idle, loading, loaded, and error states.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewIntermediateJun 25, 2026

Usage

The useImage composable owns the loading lifecycle for a single image source. Bind the returned source, onLoad, and onError to a plain image element.

vue
<script setup lang="ts">
  import { toRef } from 'vue'
  import { useImage } from '@vuetify/v0'

  const props = defineProps<{ src: string, alt: string }>()

  const { source, isLoaded, isError, onLoad, onError, retry } = useImage({
    src: toRef(() => props.src),
  })
</script>

<template>
  <img
    :alt
    :src="source"
    @load="onLoad"
    @error="onError"
  >
</template>

Architecture

useImage state machine

Use controls to zoom and pan. Click outside or press Escape to close.

useImage state machine

Reactivity

Property/MethodReactiveNotes
statusReadonly ShallowRef of 'idle' | 'loading' | 'loaded' | 'error'
isIdle / isLoading / isLoaded / isErrorReadonly boolean refs derived from status
sourceGated srcundefined while idle, otherwise the current source
onLoad / onErrorBind to image load / error events
retryReset back to loading and re-attempt

Examples

Image card with placeholder and fallback

Wrap useImage in a small composable to build a self-contained image card that drives the full state machine — idle → loading → loaded | error — and surfaces it as UI: a placeholder while loading, the photo when it arrives, a broken-image affordance on hard failure, and a status badge that reflects the live status ref. This is the “build your own smart image” pattern when the Image compound’s DOM isn’t what you need.

The interesting work happens in the wrapper. Rather than reaching for a watch on isError, the composable intercepts onError: when the primary source fails and a fallback URL exists, it swaps the gated source instead of reporting the error. Because useImage resets its state machine whenever src changes, that single assignment re-runs idle → loading → loaded | error against the fallback for free — no imperative re-fetch, no second useImage instance. A reload() method rewinds the active source back to the primary so the entire lifecycle replays on demand, demonstrating how retry() and reactive src changes compose.

Reach for this when you want consistent placeholder and fallback behavior across many images without the Image component — a gallery, a card grid, an avatar wall. The gallery wires three cards that exercise every branch: one that loads directly, one whose primary fails but recovers via fallback, and one where neither source resolves. For viewport-gated loading, compose it with useIntersectionObserver (see the next example) or defer the whole card with useLazy; for a batteries-included version, Image and Avatar package the same machine with built-in slots.

FileRole
useImageCard.tsWraps useImage, swaps the gated source to a fallback on error, and exposes reload() to replay the lifecycle
ImageCard.vueRenders one card — placeholder while loading, the image when loaded, a broken state on hard failure, a status badge, and a fallback marker
gallery.vueEntry rendering three cards: direct load, fallback recovery, and total failure
Loads directlyloading
Loading...
Fallback recoversloading
Loading...
Nothing recoversloading
Loading...

Compose with useIntersectionObserver

Wrap useImage and useIntersectionObserver in a small custom composable to build a reusable viewport-driven lazy loader. The observer returns a reactive isIntersecting flag; pipe that into useImage’s eager option and the source is withheld — status stays idle, no network request is made — until the target element scrolls into view.

Reactive signal pipeline

Use controls to zoom and pan. Click outside or press Escape to close.

Reactive signal pipeline

Reach for this pattern when the built-in <Image.Root lazy> isn’t a fit: when you’re not using the Image compound at all, when you need the observer target to be a different element than the image container, or when you want to share one observer across several images. The composable becomes the single owner of both “has it entered the viewport” and “what’s the load status” — callers just destructure { target, source, onLoad, onError, isLoaded } and wire them up.

Three things make this composition work:

  • once: true on the observer — once the element intersects, the observer disconnects. isIntersecting stays true thereafter, so the image loads once and doesn’t regress if the user scrolls it back off-screen.

  • rootMargin — start loading slightly before intersection (e.g. "200px") so the image is typically loaded by the time it’s actually visible. The default "0px" fires exactly at viewport entry, which can produce a visible blank frame on fast scrolling.

  • eager: isIntersecting — the observer’s reactive flag drives useImage’s gate directly. No manual watch, no imperative calls — Vue’s reactivity handles the state transition.

Under the hood <Image.Root lazy> does exactly this; the custom composable exists so you can apply the same pattern without the compound component.

FileRole
useLazyImage.tsCustom composable combining useImage and useIntersectionObserver — returns { target, ...image } for consumers
LazyImage.vuePresentational component binding the returned target to its root, source to the <img>, and handlers to load/error
lazy.vueEntry point rendering several lazy images in a scrolling container to demonstrate the viewport trigger

Scroll to load each image as it enters the viewport.

idle
idle
idle
idle

Retry on error

Build a reusable image component that surfaces a retry button when loading fails. Calling retry() resets the status back to loading (or idle if eager is currently false) without changing the src — the browser re-attempts the same request, which handles the common case of transient network failures, flaky CDNs, or images that aren’t in cache yet on the second attempt.

Reach for this pattern anywhere a failed image shouldn’t be a dead end: user-uploaded content that might take a moment to propagate through a CDN, photos behind a request-signed URL that can expire, or any UX where a “try again” button is friendlier than leaving a broken-image icon on screen. Track an attempts counter alongside retry when you want to cap retries or show progress (“Attempt 3 of 3”) — useImage doesn’t manage retry bookkeeping itself, which keeps it headless.

A few details worth knowing:

  • retry() is idempotent relative to src — it doesn’t change the source, just rewinds the state machine. If the image fails deterministically (404, CORS error), retry loops without progress; a fallback source or a cap is the caller’s responsibility.

  • Works with reactive src changes — swapping src also resets the state machine automatically, so you typically call retry() only when you want to re-attempt the same URL. Set a new URL via the reactive ref if you want to try a different source.

  • Pairs with the status ref — the example conditionally renders the button via isError, but you can style around data-state="error" for CSS-only treatments (e.g., a red border that appears on error).

Not limited to user-facing retries — the same pattern works for programmatic retries with backoff: watch isError, schedule a timer, call retry().

FileRole
RetryableImage.vueWraps useImage, tracks an attempts counter, and renders a retry button inside an isError branch. Simulates a flaky network: each click has a 25% chance of swapping in the real source (success) or re-requesting the broken one (another failure)
retry.vueEntry point rendering a single RetryableImage — click Retry repeatedly to watch the state cycle between loading and error until a retry happens to land on success
Loading...

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for the useImage composable.
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/