Avatars

Summary

Identity marks in seven sizes, 16 to 128px. People are round; projects and other non-person entities use the squared variant. Content is either an <img> (cover-fitted) or initials text.

When to use

SizeUse it for
-xsmall 16pxInline with text: activity feeds, mentions.
-small 24pxDense lists and table cells.
-medium 32px (default)Navbars, comments, cards.
-large 48pxProfile headers, people pickers.
-xlarge 64 / -xxlarge 96 / -xxxlarge 128pxProfile pages and detail headers.

Examples

Sizes

a an AN AN AN AN AN

All seven sizes: -xsmall 16, -small 24, -medium 32 (default), -large 48, -xlarge 64, -xxlarge 96, -xxxlarge 128px. Initials scale with the disc.

Image vs initials

Anna Nelson AN

The same person with a photo (an <img>, cover-fitted) and without: initials on the tinted disc as the fallback.

Round vs square

AN NW Nware platform

People are round; -square marks projects, apps and other non-person entities, with initials or an image.

Broken image → initials

Live — the demo JS swaps a failed photo for the fallback:

Marcus Delgado

This avatar was authored with a dead image URL; one consumer error listener replaced it with the initials form ("MD"). The component ships no JS; the pattern is in the JavaScript section below.

Image vs initials

Prefer the photo; initials are the fallback, not a style choice. Build them from the first letters of the given and family name — one or two characters, never more (the disc doesn't fit three, at any size). The <img> is cover-fitted (object-fit: cover), so any aspect ratio crops to the centre and no pre-cropping is needed.

Accessible naming follows one question: is the name printed next to the avatar? If not (the avatar is the only identification), the image's alt carries the name (initials, being real text, are already read aloud). If yes, the avatar is redundant decoration: use alt="" on the image, or aria-hidden="true" on the initials form, so the name isn't announced twice.

Sizing

Pick from the seven fixed steps. Don't free-size with inline styles; the scale exists so avatars align across tables, feeds and headers. Each size step also scales the initials type (8px at -xsmall up to 42px at -xxxlarge), so the two content forms stay interchangeable at every size — a roster can mix photos and initials without rag. Guidance per step is in the When to use table; -square combines with every size.

HTML

An image avatar, with the name in alt when the avatar is the only identification:

<span class="soma-avatar soma-avatar-medium">
  <img src="…" alt="Anna Nelson" />
</span>

The initials fallback, real text styled onto the tinted disc:

<span class="soma-avatar soma-avatar-medium soma-avatar-initials">AN</span>

Squared, for a project or app (initials or image alike):

<span class="soma-avatar soma-avatar-large soma-avatar-square soma-avatar-initials">NW</span>

Next to the printed name the avatar is decoration — silence it for screen readers:

<span class="soma-avatar soma-avatar-small soma-avatar-initials"
      aria-hidden="true">AN</span>
<a href="/people/anna">Anna Nelson</a>

CSS classes

ClassEffect
.soma-avatarBase: round, 32px, tinted disc; an <img> child is cover-fitted.
.soma-avatar-xsmall / -small / -medium / -large16 / 24 / 32 / 48px.
.soma-avatar-xlarge / -xxlarge / -xxxlarge64 / 96 / 128px, for profile pages and detail headers.
.soma-avatar-initialsStyles the text content as initials on the tinted disc; the type size follows the size step.
.soma-avatar-squareRounded-square shape for projects/apps. Combines with every size.

JavaScript

None shipped; avatars are CSS-only. The one pattern worth wiring is the image-failure fallback: when a photo 404s, swap the avatar to its initials form instead of showing a broken image.

function initialsFallback(img, initials) {
  const swap = () => {
    const avatar = img.closest('.soma-avatar');
    avatar.classList.add('soma-avatar-initials');
    avatar.textContent = initials;      // replaces the <img>
  };
  // The image may have failed before this code ran — check, then listen.
  if (img.complete && img.naturalWidth === 0) swap();
  else img.addEventListener('error', swap, { once: true });
}

The live example above, exactly as this page wires it:

const img = document.getElementById('avatar-fallback-img');
const swap = () => {
  const avatar = img.closest('.soma-avatar');
  avatar.classList.add('soma-avatar-initials');
  avatar.textContent = 'MD';
};
if (img.complete && img.naturalWidth === 0) swap();
else img.addEventListener('error', swap, { once: true });