Avatar

.avatar is a round box that holds either an image or a person's initials. One custom property, --size, drives its width, its height and the font size of the initials — calc(var(--size) * .38) — so a size variant is a single declaration and the initials never overflow.

When to use it

Use an avatar wherever a row, a comment or a message needs to be attributed to a person. It is a grid with place-items: center, so whatever goes inside is centred without any help, and overflow: hidden means an image is clipped to the circle rather than needing its own radius.

flex: 0 0 auto is set so an avatar in a flex row never shrinks when the text beside it is long. That is the one declaration that stops a list of comments from squashing every face into an oval.

Initials and an image, same class
SR
<span class="avatar">SR</span> <span class="avatar"><img alt="" src="data:image/svg+xml;utf8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22160%22 height=%22160%22%3E%3Crect width=%22160%22 height=%22160%22 fill=%22%238fa8a4%22/%3E%3Ccircle cx=%2280%22 cy=%2262%22 r=%2226%22 fill=%22%23dfe9e7%22/%3E%3Ccircle cx=%2280%22 cy=%22150%22 r=%2246%22 fill=%22%23dfe9e7%22/%3E%3C/svg%3E"></span>
<span class="avatar">SR</span>
<span class="avatar"><img alt="" src="data:image/svg+xml;utf8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22160%22 height=%22160%22%3E%3Crect width=%22160%22 height=%22160%22 fill=%22%238fa8a4%22/%3E%3Ccircle cx=%2280%22 cy=%2262%22 r=%2226%22 fill=%22%23dfe9e7%22/%3E%3Ccircle cx=%2280%22 cy=%22150%22 r=%2246%22 fill=%22%23dfe9e7%22/%3E%3C/svg%3E"></span>

Sizes

Five sizes, each setting --size and nothing else. Because the font size is computed from it, the initials scale with the circle rather than being set separately for each variant.

SR SR SR SR SR
<span class="avatar avatar-xs">SR</span> <span class="avatar avatar-sm">SR</span> <span class="avatar">SR</span> <span class="avatar avatar-lg">SR</span> <span class="avatar avatar-xl">SR</span>
<span class="avatar avatar-xs">SR</span>
<span class="avatar avatar-sm">SR</span>
<span class="avatar">SR</span>
<span class="avatar avatar-lg">SR</span>
<span class="avatar avatar-xl">SR</span>
Avatar sizes
Class--sizeInitials
.avatar-xs 24px 9.1px
.avatar-sm 32px 12.2px
.avatar 40px 15.2px
.avatar-lg 56px 21.3px
.avatar-xl 80px 30.4px

--size is a plain custom property, so a size Deck does not ship is style="--size:120px" or a class of your own. There is no need to add a variant to the framework for a one-off.

Square

.avatar-square swaps --r-full for --r-sm. Use it for anything that is not a person — an organisation, a project, a repository. Keeping people round and things square is a cheap signal that costs one class.

AC
<span class="avatar avatar-square">AC</span> <span class="avatar avatar-square avatar-lg"><img alt="" src="data:image/svg+xml;utf8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22160%22 height=%22160%22%3E%3Crect width=%22160%22 height=%22160%22 fill=%22%238fa8a4%22/%3E%3Ccircle cx=%2280%22 cy=%2262%22 r=%2226%22 fill=%22%23dfe9e7%22/%3E%3Ccircle cx=%2280%22 cy=%22150%22 r=%2246%22 fill=%22%23dfe9e7%22/%3E%3C/svg%3E"></span>
<span class="avatar avatar-square">AC</span>
<span class="avatar avatar-square avatar-lg"><img alt="" src="data:image/svg+xml;utf8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22160%22 height=%22160%22%3E%3Crect width=%22160%22 height=%22160%22 fill=%22%238fa8a4%22/%3E%3Ccircle cx=%2280%22 cy=%2262%22 r=%2226%22 fill=%22%23dfe9e7%22/%3E%3Ccircle cx=%2280%22 cy=%22150%22 r=%2246%22 fill=%22%23dfe9e7%22/%3E%3C/svg%3E"></span>

Overlapping stack

.avatar-stack is a flex row that pulls each avatar after the first back by calc(var(--size, 40px) * -.32) and gives every one a two-pixel ring in the page's surface colour, so the overlap reads as depth rather than as a collision. The offset is computed from --size, so it stays proportional at every size.

The overlap is a fraction of --size, so it scales
SR AC MJ +4
SR AC MJ
<div class="avatar-stack"> <span class="avatar avatar-sm">SR</span> <span class="avatar avatar-sm">AC</span> <span class="avatar avatar-sm">MJ</span> <span class="avatar avatar-sm">+4</span> </div> <div class="avatar-stack"> <span class="avatar avatar-lg">SR</span> <span class="avatar avatar-lg">AC</span> <span class="avatar avatar-lg">MJ</span> </div>
<div class="avatar-stack">
  <span class="avatar avatar-sm">SR</span>
  <span class="avatar avatar-sm">AC</span>
  <span class="avatar avatar-sm">MJ</span>
  <span class="avatar avatar-sm">+4</span>
</div>
<div class="avatar-stack">
  <span class="avatar avatar-lg">SR</span>
  <span class="avatar avatar-lg">AC</span>
  <span class="avatar avatar-lg">MJ</span>
</div>

The ring is box-shadow: 0 0 0 2px var(--surface). On a background that is not --surface — inside a .card-brand, say — the ring is the wrong colour. Set --surface on the stack, or accept it.

In a chat thread

src/24-media.css overrides the avatar inside .msg: 28px, align-self: flex-end so it lines up with the last line of the bubble rather than the first, and visibility: hidden on every message after the first from the same sender. The space is kept so the bubbles stay aligned; only the face is dropped.

The second avatar is hidden, not removed — the bubbles stay aligned
AC
Did the export finish?
AC
I can rerun it if not.
<div class="chat"> <div class="msg"> <span class="avatar">AC</span> <div class="bubble">Did the export finish?</div> </div> <div class="msg"> <span class="avatar">AC</span> <div class="bubble">I can rerun it if not.</div> </div> </div>
<div class="chat">
  <div class="msg">
    <span class="avatar">AC</span>
    <div class="bubble">Did the export finish?</div>
  </div>
  <div class="msg">
    <span class="avatar">AC</span>
    <div class="bubble">I can rerun it if not.</div>
  </div>
</div>

Classes

Generated from src/07-components.css by tools/docs/extract.mjs.

Classes, generated from the stylesheet source
Class Layer What it does Source
.avatar deck.components A round box holding an image or initials. --size drives the width, the height and the font size together, so a size variant is one declaration and the initials can never overflow the circle. 07-components.css:176
.avatar-lg deck.components 56px. 07-components.css:197
.avatar-sm deck.components 32px. 07-components.css:195
.avatar-square deck.components A square avatar, for anything that is not a person — an organisation, a project, a repository. 07-components.css:202
.avatar-stack deck.components Overlapping avatars. The offset is a fraction of --size, so it stays proportional at every size, and the ring is the surface colour, which is wrong on any other background. 07-components.css:207
.avatar-xl deck.components 80px. 07-components.css:199
.avatar-xs deck.components 24px. 07-components.css:193

Tokens

The avatar's fill is the brand tint, which is why a wall of initials reads as one palette rather than as noise. --size is local to the component and is the only thing a size variant touches.

Design tokens this component reads
Token Live Declared value
--brand-soft light-dark(var(--brand-50), oklch(26% .05 var(--hue-brand)))
--brand-soft-text light-dark(var(--brand-800), var(--brand-200))
--r-full 999px
--r-sm 7px
--surface light-dark(var(--ink-0), var(--ink-950))

Accessibility

  • An avatar beside a name needs empty alt text. If the person's name is already in the row, alt="" is correct — otherwise a screen reader reads the name twice. Every example on this page uses alt="" for that reason.
  • An avatar on its own needs a real name. In an .avatar-stack with no names beside it, each image needs alt="Ada Chen", or the stack is a row of unlabelled pictures.
  • Initials are read as letters. <span class="avatar">SR</span> is announced "S R", which is rarely useful. Add aria-hidden="true" when the name is beside it, or <span class="sr-only">Santos Rivera</span> when it is not.
  • user-select: none is set. Initials cannot be selected or copied, which stops a double-click on a row from highlighting "SR". It also means the initials are not available to a reader who copies the row, so the name has to exist somewhere else in the markup.
  • No focus style, because it is not interactive. An avatar that opens a profile needs to be a link or a button around the avatar, and the target needs to clear 44px — .avatar-sm at 32px does not on its own.
  • The overlap hides part of each face. In an .avatar-stack, 32% of every avatar but the last is covered. That is fine for a decorative "who is on this" summary and wrong for a list a reader has to identify people from.
  • Contrast. Initials are --brand-soft-text on --brand-soft, which clears 4.5:1 in both themes. An image avatar has no contrast requirement because it is a picture, not text.

Right to left

The stack's offset is margin-inline-start, so under dir="rtl" the avatars overlap from the other side and the first one in the markup is still the one on top. Nothing else about the component is directional — a circle has no start edge.

The overlap follows the writing direction
ا ب ج
<div dir="rtl" class="avatar-stack"> <span class="avatar">ا</span> <span class="avatar">ب</span> <span class="avatar">ج</span> </div>
<div dir="rtl" class="avatar-stack">
  <span class="avatar">ا</span>
  <span class="avatar">ب</span>
  <span class="avatar">ج</span>
</div>

Reduced motion

The avatar has no transition and no animation, so prefers-reduced-motion changes nothing about it. Nothing to report for this component.

Printing

src/99-print.css lists .avatar among the elements that keep their fill on paper, with print-color-adjust: exact. Without that, an initials avatar would print as dark letters on white and lose the circle entirely.

Overriding it

--size is the intended override and it needs no layer at all — set it inline or in your own class. Reach for app.components only to change the look.

/* A size Deck does not ship — no layer needed */
<span class="avatar" style="--size:120px">SR</span>

@layer app.components {
  .avatar {
    background: var(--ink-100);
    color: var(--text);
    font-weight: 700;
  }
}

Prefer setting --size over setting inline-size and block-size: the font size and the stack offset are both computed from it, so overriding the dimensions directly leaves the initials and the overlap at the old scale.

When not to use it

  • Not for a logo. An avatar clips to a circle and centres its contents; a wordmark comes out cropped. Use an <img> with .ratio-1x1, or .icon-tile for a square badge with a tinted background.
  • Not for an icon. Use .icon-tile. It is built for a glyph on a tint and does not compute a font size from its own width.
  • Not as a button on its own. .avatar-sm is 32px, below the 44px target. Wrap it in a .btn.btn-icon, which brings the target and the focus ring, rather than attaching a handler to the avatar.
  • Not for a list of people you have to tell apart. .avatar-stack covers a third of each face. For a real roster use a .list with an avatar and a name in each row.
  • Not for status. An avatar with a coloured ring to mean "online" is invisible to a screen reader and to anyone who cannot distinguish the colour. Use .indicator, which positions a dot and can carry .sr-only text.