Avatar
An image with a fallback for representing a user.
CNERRRSM
CNRRAL
+3
First image: idle
Codeapp/demos/public/avatar.tsx
import { clientEntry, type Handle } from 'remix/component'
import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
AvatarImage,
type ImageLoadingStatus,
} from '@stemsui/registry/ui/avatar.tsx'
export const AvatarDemo = clientEntry(import.meta.url, function AvatarDemo(handle: Handle) {
let status: ImageLoadingStatus = 'idle'
return () => (
<div class="flex flex-col items-center gap-6">
<div class="flex flex-wrap items-center justify-center gap-4">
<Avatar>
<AvatarImage
src="https://github.com/shadcn.png"
alt="@shadcn"
onLoadingStatusChange={(next) => {
status = next
void handle.update()
}}
/>
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="/missing-avatar.png" alt="Broken image" />
<AvatarFallback>ER</AvatarFallback>
</Avatar>
<Avatar size="lg">
<AvatarImage src="https://github.com/remix-run.png" alt="@remix-run" />
<AvatarFallback delay={600}>RR</AvatarFallback>
<AvatarBadge />
</Avatar>
<Avatar size="sm">
<AvatarFallback>SM</AvatarFallback>
</Avatar>
</div>
<AvatarGroup>
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://github.com/remix-run.png" alt="@remix-run" />
<AvatarFallback>RR</AvatarFallback>
</Avatar>
<Avatar>
<AvatarFallback>AL</AvatarFallback>
</Avatar>
<AvatarGroupCount>+3</AvatarGroupCount>
</AvatarGroup>
<p class="text-sm text-muted-foreground" data-testid="avatar-status">
First image: {status}
</p>
</div>
)
})
Installation
npx shadcn add @stemsui/avatarRequires the one-time setup. Behavior: stems-ui/avatar.
API reference
The styled component is built on the headless module below: its props mirror these options, and the same events and data attributes apply.
stems-ui/avatar
avatar shows a user's image, with a fallback (initials or an icon) while the image is missing,
loading, or broken.
Base UI equivalent: Avatar.
Usage
import * as avatar from 'stems-ui/avatar'
function UserAvatar() {
return () => (
<avatar.Context>
<span class="avatar" mix={avatar.root()}>
<img src="/ada.png" alt="Ada Lovelace" mix={avatar.image()} />
<span mix={avatar.fallback({ delay: 600 })}>AL</span>
</span>
</avatar.Context>
)
}
stems-ui/avatar
<avatar.Context>: owns the image loading status shared by the parts. No props besideschildren.avatar.root(): the root element. Adds no attributes; it mirrors Base UI's anatomy.avatar.image(options?): put on an<img>. Readssrcfrom the host.onLoadingStatusChange(status): called with'loading', then'loaded'or'error'('idle'whensrcis removed).keepMounted: keep the image visible while it loads in place, instead of hiding it until a preload finishes. Use withloading="lazy"and stack the image over the fallback; hide it withdata-loading/data-error(prefervisibilityoverdisplay: none, which would stop lazy loading). Defaults tofalse.
avatar.fallback(options?)delay: milliseconds to wait before showing the fallback. Defaults to0.
- Types:
ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'.
Behavior notes
- Mixins can't add or remove their host, so the image stays in the DOM. By default it is
hiddenuntil anew Image()preload ofsrcsucceeds (Base UI only renders it then). While not loaded it also hasaria-hidden="true", so the fallback names the avatar. - The fallback is
hiddenonce the image has loaded, and before itsdelayhas passed. - Server rendering: an image with
srcrendershiddenwithdata-loading, and the fallback renders visible (or hidden, with adelay), so there is no flash of a broken or half-loaded image. Loading starts after hydration. - Changing
srcrestarts loading. image()andfallback()also work without<avatar.Context>, but then the fallback can't see the image status and only honors itsdelay.- The
hiddenattribute needs a stylesheet that doesn't override it (Tailwind's preflight makes[hidden]display: none !important).
Styling hooks
| Part | Attribute | Values |
|---|---|---|
image | data-loading | Present while the image is loading. |
image | data-error | Present when the image failed to load. |
image | hidden | Present until loaded (not keepMounted). |
fallback | hidden | Present when the image has loaded, or during delay. |
Base UI's data-starting-style / data-ending-style on the image are not implemented yet.

