Show an image with a fallback while it loads or if it fails.

Use an Avatar for a person or a team, like in a comment, a member list or an account menu. It shows initials or an icon until the image loads, and keeps showing them if the image fails.

import { Avatar, AvatarFallback, AvatarImage } from "@ui-sdk/react/avatar"

const person = {
  name: "shadcn",
  src: "https://github.com/shadcn.png",
}

export function Example() {
  return (
    <div data-slot="row">
      <Avatar>
        <AvatarImage src={person.src} alt={person.name} />
        <AvatarFallback>
          <span aria-hidden="true">CN</span>
          <span data-slot="visually-hidden">shadcn</span>
        </AvatarFallback>
      </Avatar>
    </div>
  )
}
The image replaces the initials once it loads. A screen reader can read the person's name in either state.

Installation

npm install @ui-sdk/react @ui-sdk/core

Composition

Use the following composition to build an Avatar:

Avatar
├── AvatarImage
└── AvatarFallback
  • Avatar shares the image's loading state with its parts.
  • AvatarImage renders the image.
  • AvatarFallback shows when no image is loaded.

The image is a real img, including in server markup. The browser can start fetching it before hydration. The fallback stays visible until the image loads; if loading fails, the fallback remains.

Usage

Import the parts and put them together:

<Avatar>
  <AvatarImage src="/ada.png" alt="Ada Lovelace" />
  <AvatarFallback>AL</AvatarFallback>
</Avatar>

Build Your Own

@ui-sdk/react/avatar comes with a ready-made Avatar. If you'd like to build your own, with your own names, start from the avatar primitive in @ui-sdk/core:

user-photo.ts
"use client"

import { avatar } from "@ui-sdk/core/avatar"
import { createParts } from "@ui-sdk/react/primitive"

const {
  Root: UserPhoto,
  Image: UserPhotoImage,
  Fallback: UserPhotoInitials,
} = createParts(avatar, { root: "span", image: "img", fallback: "span" })

export { UserPhoto, UserPhotoImage, UserPhotoInitials }
<UserPhoto>
  <UserPhotoImage src="https://github.com/shadcn.png" alt="shadcn" />
  <UserPhotoInitials>CN</UserPhotoInitials>
</UserPhoto>

These are the same parts the ready-made Avatar is made of, with your names. See Rendering and Custom Rendering for more.

Fallback Delay

Set delay on the fallback to wait before showing it. This avoids flashing initials when an image loads quickly. Without a delay, the fallback appears immediately, including on the server.

<Avatar>
  <AvatarImage src="https://github.com/shadcn.png" alt="shadcn" />
  <AvatarFallback delay={600}>CN</AvatarFallback>
</Avatar>

Image Attributes

Pass image attributes such as srcSet, crossOrigin, and referrerPolicy to the image. They work on the same img the browser loads. For avatars farther down a page, use loading="lazy".

<Avatar>
  <AvatarImage
    src="https://github.com/shadcn.png"
    srcSet="https://github.com/shadcn.png?size=80 1x, https://github.com/shadcn.png?size=160 2x"
    alt="shadcn"
    crossOrigin="anonymous"
    referrerPolicy="no-referrer"
    loading="lazy"
  />
  <AvatarFallback>CN</AvatarFallback>
</Avatar>

Interactive Avatar

Use a button when clicking an avatar performs an action. Give it an accessible name that stays available while the image loads.

<Avatar
  render={<button type="button" aria-label="shadcn" />}
  onClick={openProfile}
>
  <AvatarImage src="https://github.com/shadcn.png" alt="" />
  <AvatarFallback aria-hidden>CN</AvatarFallback>
</Avatar>

Parts and Composition explains how to change the root element in this framework.

Loading Status

onLoadingStatusChange receives the new status and change details. The possible statuses are loading, loaded, error, and idle.

<Avatar
  onLoadingStatusChange={(status, details) => {
    if (status === "error") reportBrokenImage(details.reason)
  }}
>
  <AvatarImage src={user.image} alt={user.name} />
  <AvatarFallback>{user.initials}</AvatarFallback>
</Avatar>

When you replace a loaded image, the browser keeps it visible while the next source loads. A failed replacement shows the fallback. The change details explain why the status changed; see Reasons.

Custom Rendering

Use Parts and Composition to replace an element or read its state. Every part receives the loading status, so the fallback can say something different when the image fails:

<Avatar>
  <AvatarImage src={user.image} alt={user.name} />
  <AvatarFallback>
    {(state) => (state.loadingStatus === "error" ? "?" : user.initials)}
  </AvatarFallback>
</Avatar>

Styling

Set the root's size and let the image and fallback fill it. The image manages its own hidden styles until it loads; keep those styles intact when changing its element.

Each part has a data-slot, such as avatar-fallback, and shows the loading status through the attributes listed under Data Attributes.

Data Attributes

Avatar

[data-slot="avatar"]

AttributeWhat it means
data-loading-statusidleloadingloadederrorWhere the image is: no image yet, loading, loaded, or failed.

AvatarImage

[data-slot="avatar-image"]

AttributeWhat it means
data-loading-statusidleloadingloadederrorWhere the image is: no image yet, loading, loaded, or failed.

AvatarFallback

[data-slot="avatar-fallback"]

AttributeWhat it means
data-loading-statusidleloadingloadederrorWhere the image is: no image yet, loading, loaded, or failed.

API Reference

Props and callbacks are the same as below, as React props.

Avatar

onLoadingStatusChange?((status: AvatarLoadingStatus, details: ChangeDetails<AvatarLoadingStatusChangeReason>) => void)
Runs when the image's loading status changes: "loading" when it gets a source, "loaded", "error", and "idle" when it leaves the page. details.reason says why, and details.event is the image's load or error event, or undefined when the status was known at once, like for a cached image.
debug?boolean
Logs every state change with its reason to the console, in development builds.

AvatarImage

The image. Renders a real img, so the browser can fetch it before hydration, and accepts its attributes, such as srcSet, crossOrigin and referrerPolicy.

AvatarFallback

delay?number
How long to wait, in milliseconds, before showing the fallback, so a fast image appears without the fallback flashing first. Without one, the fallback shows at once, server markup included.

State

What render functions, state-based class names and slots receive. Every part gets the same state.

loadingStatusAvatarLoadingStatus
Where the image is: "idle", "loading", "loaded" or "error".

Reasons

details.reason says why the status changed.

image-loadEvent | undefined
An image loaded. No event when it was already in the browser's cache.
image-errorEvent | undefined
An image failed to load, or has no source. No event when that was known at once.
src-changenone
An image got a new source, or left the page, so its status starts over.

Events

Callbacks get the new status first, then the details.

PropOnReceives
onLoadingStatusChangeAvatar(status, details): the image's status, the reason and details.event

Accessibility

Give the image meaningful alt text. Include the person's name in the fallback too, using visually hidden text if you only want to show initials.

When a visible name beside the avatar already identifies the person, use an empty alt and hide decorative fallback content from screen readers.

  • An image that loads replaces the fallback.
  • An image that fails keeps the fallback.
  • An avatar without an image shows its fallback.
  • A fallback with a delay waits before showing.
  • The image's loading status is reported.
  • Removing the image brings the fallback back.
  • A new source shows its image once it loads.
  • The image is fetched with its own referrer policy.
  • A cached image doesn't flash the fallback when the avatar mounts again.
  • A new source keeps showing the previous image until it loads.
  • The image is fetched once, even when it can't be cached.

Runs in React, Vue and web components.