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".
<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.
<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".
<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.
<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.
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'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.
<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.
<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 emptyalt=""when the avatar is purely decorative (e.g., individual avatars inside a labeled.avatar-group). -
Initials are visible text; no
aria-labelneeded 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-labelon the group root ("Team: Alex, Sam, Casey, and 4 others") and emptyalt=""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
| Token | Default | Description |
|---|---|---|
--avatar-size | 2.5rem | Diameter of the avatar disc |
--avatar-radius | var(--radius-pill) | Corner radius of the avatar |
--avatar-bg | var(--color-fill-strong) | Background color for the avatar disc |
--avatar-color | var(--color-text-muted) | Text and icon color inside the avatar |
--avatar-status-size | calc(var(--avatar-size) * 0.3) | Diameter of the status indicator dot |
--avatar-status-ring | var(--color-surface) | Ring color around the status dot for background separation |
Classes
| Class | Description |
|---|---|
.avatar | component root |
.is-sm | Small size variant (1.75rem) |
.is-lg | Large size variant (3.5rem) |
.is-xl | Extra-large size variant (5rem) |
.as-square | Square shape for entity or team avatars |
CSS Reference: Avatar Group
Overlapping stack of avatars
Source: src/css/extras/avatar.css
Tokens
| Token | Default | Description |
|---|---|---|
--avatar-group-offset | -0.75rem | Negative inline margin for avatar overlap |
--avatar-group-border | 2px solid var(--color-surface) | Border between overlapping avatars for visual separation |
Classes
| Class | Description |
|---|---|
.avatar-group | component root |
CSS Reference: Avatar Status
Status indicator dot
Source: src/css/extras/avatar.css
Classes
| Class | Description |
|---|---|
.avatar-status | component root |
.is-online | Online status, colored with success |
.is-away | Away status, colored with warning |
.is-busy | Busy status, colored with danger |
.is-offline | Offline status, colored with subtle text |