Skip to main content
Vuetify0 is now in alpha!
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.

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

Basic usage

Wire useImage directly to a plain <img> element. The composable returns source, onLoad, onError, and four state refs — bind them to the element and you have the full load lifecycle for ~10 lines of setup.

Reach for raw useImage when the image lives in markup the Image compound can’t produce — hand-rolled <picture>, a canvas-adjacent element, third-party wrappers, or anywhere you want the state machine without v0’s DOM. For typical content images, prefer Image.Root + Image.Img which packages the same composable with built-in placeholder and fallback slots.

The example surfaces the status ref as a live label and includes buttons that swap between a working URL and a known-broken URL, so the transitions idle → loading → loaded | error play out on click.

Loading...

Status: 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

API Reference

The following API details are for the useImage composable.

Functions

useImage

(options: UseImageOptions) => UseImageReturn

Tracks image loading state as a reactive state machine.

Options

src required

MaybeRefOrGetter<string | undefined>

Image source URL. Changes trigger a reload.

eager

MaybeRefOrGetter<boolean> | undefined

Whether the image should load. When `false`, status stays `idle` and `source` resolves to `undefined` so the browser does not start loading.

Default: true

Properties

status

Readonly<ShallowRef<ImageStatus>>

Current loading state.

isIdle

Readonly<Ref<boolean, boolean>>

Whether the image has not yet started loading (eager is false).

isLoading

Readonly<Ref<boolean, boolean>>

Whether the image is currently loading.

isLoaded

Readonly<Ref<boolean, boolean>>

Whether the image has loaded successfully.

isError

Readonly<Ref<boolean, boolean>>

Whether the image failed to load.

source

Readonly<Ref<string | undefined, string | undefined>>

Gated source URL. Resolves to `undefined` while idle, otherwise the current `src` value. Bind directly to the `<img>` `src` attribute.

Methods

onLoad

(e?: Event) => void

Bind to the `<img>` `load` event.

onError

(e?: Event) => void

Bind to the `<img>` `error` event.

retry

() => void

Reset status back to `loading` so the browser re-attempts the source.

Was this page helpful?

© 2016-1970 Vuetify, LLC
Ctrl+/