Basic

Avatar with image and fallback.

@shadcn CN JD
<span class="avatar">
  <img class="avatar-image" src="..." alt="@shadcn" />
  <span class="avatar-fallback">CN</span>
</span>

Sizes

Small, default, and large variants.

@shadcn CN @shadcn CN @shadcn CN
<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.

@shadcn CN JD
<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.

@shadcn CN @maxleiter ML @evilrabbit ER +3
<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.

@shadcn CN

Cody Lindley

cody@example.com

Admin
<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>

CSS view file

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;
  }
}

JavaScript view file

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 });