Avatar
FeedbackLocal guide ↗
import { Avatar } from "bits-ui" Loaded image
A self-contained SVG portrait with the root's live loading status
AM
Avery Morgan
Design systems
loading<script lang="ts">
import { Avatar } from 'bits-ui';
const portrait = `data:image/svg+xml,${encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop stop-color="#20c7bd"/>
<stop offset="1" stop-color="#7c3aed"/>
</linearGradient>
</defs>
<rect width="160" height="160" rx="80" fill="url(#bg)"/>
<circle cx="80" cy="62" r="29" fill="#f8fafc" fill-opacity=".92"/>
<path d="M28 148c7-34 26-51 52-51s45 17 52 51" fill="#f8fafc" fill-opacity=".92"/>
</svg>
`)}`;
let loadingStatus = $state<'loading' | 'loaded' | 'error'>('loading');
</script>
<div class="flex items-center justify-center gap-5">
<Avatar.Root
bind:loadingStatus
delayMs={120}
class="size-20 overflow-hidden rounded-full border-2 border-ink-700 bg-ink-850 data-[status=loaded]:border-brand-500"
>
<Avatar.Image src={portrait} alt="Avery Morgan" class="size-full object-cover" />
<Avatar.Fallback
class="flex size-full items-center justify-center text-lg font-semibold text-ink-200"
>
AM
</Avatar.Fallback>
</Avatar.Root>
<div>
<p class="font-semibold text-ink-50">Avery Morgan</p>
<p class="muted mt-0.5 text-sm">Design systems</p>
<span class="chip mt-2 capitalize">{loadingStatus}</span>
</div>
</div>
Broken source fallback
An intentionally invalid local URL exposes loading, error and fallback behaviour
···
Deliberately unavailable source
loadingStatus: loading
<script lang="ts">
import { Avatar } from 'bits-ui';
let attempt = $state(1);
let loadingStatus = $state<'loading' | 'loaded' | 'error'>('loading');
const brokenSrc = $derived(`/avatars/deliberately-missing-${attempt}.svg`);
function retry() {
loadingStatus = 'loading';
attempt += 1;
}
</script>
<div class="grid justify-items-center gap-4 text-center">
<Avatar.Root
bind:loadingStatus
class="size-20 overflow-hidden rounded-full border-2 border-ink-700 bg-ink-850 data-[status=error]:border-accent-500 data-[status=loading]:border-brand-500"
>
<Avatar.Image src={brokenSrc} alt="Noor Rahman's unavailable portrait" class="size-full object-cover" />
<Avatar.Fallback
class="flex size-full items-center justify-center text-lg font-semibold text-ink-50 data-[status=loading]:animate-pulse data-[status=loading]:text-brand-400"
>
{loadingStatus === 'loading' ? '···' : 'NR'}
</Avatar.Fallback>
</Avatar.Root>
<div>
<p class="font-medium text-ink-50">Deliberately unavailable source</p>
<p class="muted mt-1 text-xs">
loadingStatus: <span class="font-mono text-ink-200">{loadingStatus}</span>
</p>
</div>
<button class="btn btn-sm" onclick={retry}>Retry broken image</button>
</div>
Avatar group
Loaded data-URL portraits overlap without relying on a remote image host
4 collaborators
Hover a portrait for the name
<script lang="ts">
import { Avatar } from 'bits-ui';
function portrait(colorA: string, colorB: string, mark: string) {
return `data:image/svg+xml,${encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop stop-color="${colorA}"/><stop offset="1" stop-color="${colorB}"/></linearGradient></defs>
<rect width="96" height="96" rx="48" fill="url(#g)"/>
<text x="48" y="58" text-anchor="middle" font-family="system-ui" font-size="29" font-weight="700" fill="white">${mark}</text>
</svg>
`)}`;
}
const members = [
{ name: 'Mina Lee', initials: 'ML', src: portrait('#0f766e', '#22d3ee', 'ML') },
{ name: 'Jon Bell', initials: 'JB', src: portrait('#7c3aed', '#ec4899', 'JB') },
{ name: 'Sam Ortiz', initials: 'SO', src: portrait('#c2410c', '#facc15', 'SO') },
{ name: 'Ivy Chen', initials: 'IC', src: portrait('#1d4ed8', '#8b5cf6', 'IC') }
];
</script>
<div class="flex flex-wrap items-center justify-center gap-4">
<div class="flex items-center" aria-label="Project members">
{#each members as member, index (member.name)}
<Avatar.Root
loadingStatus="loaded"
class="relative size-12 overflow-hidden rounded-full border-2 border-ink-950 bg-ink-850 shadow-md {index > 0 ? '-ml-3' : ''}"
style={`z-index: ${members.length - index}`}
title={member.name}
>
<Avatar.Image src={member.src} alt={member.name} class="size-full object-cover" />
<Avatar.Fallback
class="flex size-full items-center justify-center text-xs font-semibold text-ink-50"
>
{member.initials}
</Avatar.Fallback>
</Avatar.Root>
{/each}
</div>
<div>
<p class="text-sm font-medium text-ink-50">4 collaborators</p>
<p class="muted text-xs">Hover a portrait for the name</p>
</div>
</div>
loadingStatus is bindable and moves through loading, loaded and error; the same value is exposed as data-status on all three parts. The fallback is rendered while loading and after an error. For a source known to be available, setting loadingStatus="loaded" skips the image load check; delayMs can otherwise delay revealing a loaded image.