Avatar

Part of the extras bundle, not included by default.

Intent

A small disc that identifies a user or entity. The content is whichever child the consumer authors -- an <img>, a span of initials, or an icon -- and the disc paints a neutral fill behind whatever's there. Sizes track the surrounding density (timeline marker, list row, profile header). An optional status dot signals presence; an overlapping group reads as a team or shared resource.

The default fill is neutral (--color-fill-strong), so a list of avatars doesn't read as a wash of brand color. Override --avatar-bg per-instance when consumers hash a color from the user's name for visual differentiation.

Basic usage

The .avatar class wraps whichever child the consumer authors. Image avatars need a meaningful alt; initials don't need an aria-label since the visible letters are the content; decorative icons take aria-hidden="true".

Jane Doe
JD
<div class="cluster">
    <div class="avatar">
        <img src="https://picsum.photos/seed/jane/96/96" alt="Jane Doe"/>
    </div>
    <div class="avatar">
        <span>JD</span>
    </div>
    <div class="avatar">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
            <circle cx="12" cy="7" r="4"></circle>
        </svg>
    </div>
</div>

Sizes

Four tiers cover the common cases: .is-sm for inline mentions and dense list rows, default for general application chrome, .is-lg for prominent profile cards, and .is-xl for profile-page headers. Override --avatar-size for one-offs.

SM
MD
LG
XL
<div class="cluster">
    <div class="avatar is-sm"><span>SM</span></div>
    <div class="avatar"><span>MD</span></div>
    <div class="avatar is-lg"><span>LG</span></div>
    <div class="avatar is-xl"><span>XL</span></div>
</div>

Shapes

Default is a circle (--radius-pill); the .as-square variant rounds to --radius-md for entity, team, or organization avatars where the square shape reads as "thing" rather than "person".

Circular user avatar
Square team avatar
AC
<div class="cluster">
    <div class="avatar"><img src="https://picsum.photos/seed/circle/96/96" alt="Circular user avatar"/></div>
    <div class="avatar as-square"><img src="https://picsum.photos/seed/square/96/96" alt="Square team avatar"/></div>
    <div class="avatar as-square"><span>AC</span></div>
</div>

Status indicator

Add a .avatar-status child to paint a colored presence dot at the bottom-end corner. Variants (.is-online, .is-away, .is-busy, .is-offline) remap the dot's background; the dot itself is a small absolutely-positioned span with no inherent semantics. Add an aria-label on the dot, or describe the state in adjacent text, when the meaning matters to assistive tech.

Online user
Away user
Busy user
OF
<div class="cluster">
    <div class="avatar">
        <img src="https://picsum.photos/seed/u1/96/96" alt="Online user"/>
        <span class="avatar-status is-online" aria-label="Online"></span>
    </div>
    <div class="avatar">
        <img src="https://picsum.photos/seed/u2/96/96" alt="Away user"/>
        <span class="avatar-status is-away" aria-label="Away"></span>
    </div>
    <div class="avatar">
        <img src="https://picsum.photos/seed/u3/96/96" alt="Busy user"/>
        <span class="avatar-status is-busy" aria-label="Busy"></span>
    </div>
    <div class="avatar">
        <span>OF</span>
        <span class="avatar-status is-offline" aria-label="Offline"></span>
    </div>
</div>

The status dot's surrounding ring defaults to --color-surface -- the page background. When the avatar sits inside a card or other elevated container, override --avatar-status-ring on the avatar (or on a surrounding scope) so the ring matches its container.

Online user

Status dot's ring picks up the card surface.

<div class="card cluster" style="--avatar-status-ring: var(--color-surface-raised)">
    <div class="avatar">
        <img src="https://picsum.photos/seed/c1/96/96" alt="Online user"/>
        <span class="avatar-status is-online" aria-label="Online"></span>
    </div>
    <div>
        <p>Status dot&#39;s ring picks up the card surface.</p>
    </div>
</div>

Group

.avatar-group stacks avatars with a small overlapping offset, separated by a 2px ring of page surface so each disc reads as distinct. Source order is visual order; the last avatar in source paints on top of the overlap, so the conventional "+N more" overflow lands rightmost AND on top without extra wiring.

Wrap the group in an aria-label when assistive tech should hear the full membership; the individual avatars inside are decorative when the label carries the meaning.

+4
<div class="avatar-group" aria-label="Team: Alex, Sam, Casey, and 4 others">
    <div class="avatar"><img src="https://picsum.photos/seed/g1/96/96" alt=""/></div>
    <div class="avatar"><img src="https://picsum.photos/seed/g2/96/96" alt=""/></div>
    <div class="avatar"><img src="https://picsum.photos/seed/g3/96/96" alt=""/></div>
    <div class="avatar"><span>+4</span></div>
</div>

Inside a card, override --avatar-group-border so the separating ring matches the card surface instead of the page.

Reviewed by

<div class="card" style="--avatar-group-border: 2px solid var(--color-surface-raised)">
    <div class="stack is-snug">
        <p class="text-muted">Reviewed by</p>
        <div class="avatar-group" aria-label="Reviewers: Alex, Sam, Casey">
            <div class="avatar is-sm"><img src="https://picsum.photos/seed/r1/64/64" alt=""/></div>
            <div class="avatar is-sm"><img src="https://picsum.photos/seed/r2/64/64" alt=""/></div>
            <div class="avatar is-sm"><img src="https://picsum.photos/seed/r3/64/64" alt=""/></div>
        </div>
    </div>
</div>

Customization

Background, color, size, and radius all flow through scoped --avatar-* tokens. The most common override is --avatar-bg for hash-by-name color variety in user lists -- pair it with a matching --avatar-color on the same instance so initials stay legible. Status dot sizing follows the avatar size by default (calc(var(--avatar-size) * 0.3)); override --avatar-status-size to disconnect them. The group's overlap and ring are independently scoped on .avatar-group so stacking density and inset color can be tuned without touching individual avatars.

JD
AM
RK
PT
<div class="cluster">
    <div class="avatar" style="--avatar-bg: oklch(85% 0.12 30); --avatar-color: oklch(30% 0.15 30)">
        <span>JD</span>
    </div>
    <div class="avatar" style="--avatar-bg: oklch(85% 0.12 130); --avatar-color: oklch(30% 0.15 130)">
        <span>AM</span>
    </div>
    <div class="avatar" style="--avatar-bg: oklch(85% 0.12 230); --avatar-color: oklch(30% 0.15 230)">
        <span>RK</span>
    </div>
    <div class="avatar" style="--avatar-bg: oklch(85% 0.12 330); --avatar-color: oklch(30% 0.15 330)">
        <span>PT</span>
    </div>
</div>

Accessibility

  • Image avatars need a meaningful alt. Use the person's name for user avatars, the entity's name for team / org avatars, or empty alt="" when the avatar is purely decorative (e.g., individual avatars inside a labeled .avatar-group).
  • Initials are visible text; no aria-label needed unless the visible string doesn't match the announced name (e.g., abbreviations that screen readers would mis-pronounce).
  • Decorative icons take aria-hidden="true" so assistive tech doesn't announce them.
  • Status dots have no inherent semantics -- add aria-label ("Online", "Away", etc.) on the dot, or describe the state in adjacent text, when the meaning matters.
  • Overlapping groups with a "+N" overflow usually want one aria-label on the group root ("Team: Alex, Sam, Casey, and 4 others") and empty alt="" on the inner avatars -- the label carries the membership; the visuals are decorative.

Markup

<!-- Image with alt -->
<div class="avatar"><img src="..." alt="Jane Doe"></div>

<!-- Initials -->
<div class="avatar"><span>JD</span></div>

<!-- Decorative icon -->
<div class="avatar"><svg aria-hidden="true">...</svg></div>

<!-- With presence -->
<div class="avatar">
    <img src="..." alt="Jane Doe">
    <span class="avatar-status is-online" aria-label="Online"></span>
</div>

<!-- Overlapping group -->
<div class="avatar-group" aria-label="Team: Alex, Sam, Casey, +4 others">
    <div class="avatar"><img src="..." alt=""></div>
    <div class="avatar"><img src="..." alt=""></div>
    <div class="avatar"><img src="..." alt=""></div>
    <div class="avatar"><span>+4</span></div>
</div>

CSS Reference: Avatar

User or entity image with fallback initials or icon

Source: src/css/extras/avatar.css

Tokens

TokenDefaultDescription
--avatar-size2.5remDiameter of the avatar disc
--avatar-radiusvar(--radius-pill)Corner radius of the avatar
--avatar-bgvar(--color-fill-strong)Background color for the avatar disc
--avatar-colorvar(--color-text-muted)Text and icon color inside the avatar
--avatar-status-sizecalc(var(--avatar-size) * 0.3)Diameter of the status indicator dot
--avatar-status-ringvar(--color-surface)Ring color around the status dot for background separation

Classes

ClassDescription
.avatarcomponent root
.is-smSmall size variant (1.75rem)
.is-lgLarge size variant (3.5rem)
.is-xlExtra-large size variant (5rem)
.as-squareSquare shape for entity or team avatars

CSS Reference: Avatar Group

Overlapping stack of avatars

Source: src/css/extras/avatar.css

Tokens

TokenDefaultDescription
--avatar-group-offset-0.75remNegative inline margin for avatar overlap
--avatar-group-border2px solid var(--color-surface)Border between overlapping avatars for visual separation

Classes

ClassDescription
.avatar-groupcomponent root

CSS Reference: Avatar Status

Status indicator dot

Source: src/css/extras/avatar.css

Classes

ClassDescription
.avatar-statuscomponent root
.is-onlineOnline status, colored with success
.is-awayAway status, colored with warning
.is-busyBusy status, colored with danger
.is-offlineOffline status, colored with subtle text