Cody Lindley
cody@example.com
An image element with a fallback for representing the user. Supports sizes, status badges, and avatar groups.
Basic
Avatar with image and fallback.
<span class="avatar">
<img class="avatar-image" src="..." alt="@shadcn" />
<span class="avatar-fallback">CN</span>
</span>
Sizes
Small, default, and large variants.
<span class="avatar" data-size="sm">...</span>
<span class="avatar">...</span>
<span class="avatar" data-size="lg">...</span>
With Badge
Status indicator badge positioned at the bottom-right.
<span class="avatar">
<img class="avatar-image" src="..." alt="@shadcn" />
<span class="avatar-fallback">CN</span>
<span class="avatar-badge"></span>
</span>
Avatar Group
Overlapping avatars with an optional count.
<div class="avatar-group">
<span class="avatar">
<img class="avatar-image" src="..." alt="..." />
<span class="avatar-fallback">CN</span>
</span>
<span class="avatar">...</span>
<span class="avatar">...</span>
<span class="avatar-group-count">+3</span>
</div>
In a Card
Avatar composed with .card, .badge, and .btn for a user profile pattern.
Cody Lindley
cody@example.com
<article class="card">
<div class="card-header">
<span class="avatar">...</span>
<div>Name / Email</div>
<span class="badge" data-variant="secondary">Admin</span>
</div>
<div class="card-footer">
<button class="btn" data-variant="outline">View Profile</button>
</div>
</article>
Hide fallback when image is loaded
@layer components {
.avatar {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 9999px;
overflow: hidden;
flex-shrink: 0;
background-color: var(--muted);
&[data-size="sm"] {
width: 2rem;
height: 2rem;
font-size: 0.6875rem;
}
&[data-size="lg"] {
width: 3rem;
height: 3rem;
font-size: 0.875rem;
}
}
.avatar-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
}
.avatar-fallback {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
font-size: 0.75rem;
font-weight: 500;
color: var(--muted-foreground);
background-color: var(--muted);
position: absolute;
inset: 0;
}
/* Hide fallback when image is loaded */
.avatar:has(.avatar-image:not([data-error])) .avatar-fallback {
display: none;
}
.avatar-badge {
position: absolute;
bottom: 0;
right: 0;
width: 0.625rem;
height: 0.625rem;
border-radius: 9999px;
background-color: #16a34a;
border: 2px solid var(--background);
}
/* Avatar group */
.avatar-group {
display: flex;
align-items: center;
& .avatar {
border: 2px solid var(--background);
margin-left: -0.5rem;
&:first-child {
margin-left: 0;
}
}
}
.avatar-group-count {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 9999px;
background-color: var(--muted);
color: var(--muted-foreground);
font-size: 0.75rem;
font-weight: 500;
border: 2px solid var(--background);
margin-left: -0.5rem;
}
}
Interaction logic for the avatar component. Uses data attributes for wiring.
// -- Avatar ---------------------------------------------------
// Hides broken avatar images and shows the fallback.
function init() {
document.querySelectorAll('.avatar-image:not([data-init])').forEach((img) => {
img.dataset.init = '';
img.addEventListener('error', () => {
img.setAttribute('data-error', '');
img.style.display = 'none';
});
});
}
init();
new MutationObserver(init).observe(document, { childList: true, subtree: true });