Skip to content

role presentation

Avatar draft v0.1.0

Represents a person or an entity with a picture, or their initials when there is none.

An avatar shows who or what something belongs to: the author of a comment, the members of a project, a workspace. It displays a picture when there is one and the initials of the name otherwise, in a circle or a rounded square. Assistive technology reads it as an image named by the person, unless the name is written right next to it, in which case the avatar is decoration.

Web implementation

Root .ds-avatar on a <span>. Properties map to attributes: size → data-sizeshape → data-shapename → aria-label

packages/components/src/components/avatar/avatar.css
/*
 * Avatar — vanilla CSS reference implementation.
 * Spec:   packages/components/specs/components/avatar.spec.json
 * Tokens: packages/tokens/dist/css/components/avatar.css (generated from packages/tokens/src/components/avatar.tokens.json)
 *
 * Rules:
 *  - Only --avatar-* custom properties may appear here. No raw values, no semantic tokens.
 *  - The root is a span exposed as an image named by the person; it holds the picture (which
 *    covers the whole shape) or the initials. Size and shape are data attributes; no interaction state.
 */

.ds-avatar {
  --_size: var(--avatar-container-size-medium);
  --_radius: var(--avatar-container-border-radius-circle);
  --_font-family: var(--avatar-initials-typography-medium-font-family);
  --_font-size: var(--avatar-initials-typography-medium-font-size);
  --_font-weight: var(--avatar-initials-typography-medium-font-weight);
  --_line-height: var(--avatar-initials-typography-medium-line-height);
  --_letter-spacing: var(--avatar-initials-typography-medium-letter-spacing);

  box-sizing: border-box;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  inline-size: var(--_size);
  block-size: var(--_size);
  overflow: hidden;
  border: var(--avatar-container-border-width) solid var(--avatar-container-border-color);
  border-radius: var(--_radius);
  background-color: var(--avatar-container-background);
  color: var(--avatar-initials-color);
  vertical-align: middle;
  user-select: none;
}

.ds-avatar[data-size="small"] {
  --_size: var(--avatar-container-size-small);
  --_font-family: var(--avatar-initials-typography-small-font-family);
  --_font-size: var(--avatar-initials-typography-small-font-size);
  --_font-weight: var(--avatar-initials-typography-small-font-weight);
  --_line-height: var(--avatar-initials-typography-small-line-height);
  --_letter-spacing: var(--avatar-initials-typography-small-letter-spacing);
}

.ds-avatar[data-size="large"] {
  --_size: var(--avatar-container-size-large);
  --_font-family: var(--avatar-initials-typography-large-font-family);
  --_font-size: var(--avatar-initials-typography-large-font-size);
  --_font-weight: var(--avatar-initials-typography-large-font-weight);
  --_line-height: var(--avatar-initials-typography-large-line-height);
  --_letter-spacing: var(--avatar-initials-typography-large-letter-spacing);
}

.ds-avatar[data-shape="square"] {
  --_radius: var(--avatar-container-border-radius-square);
}

.ds-avatar__image {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.ds-avatar__initials {
  font-family: var(--_font-family);
  font-size: var(--_font-size);
  font-weight: var(--_font-weight);
  line-height: var(--_line-height);
  letter-spacing: var(--_letter-spacing);
  text-transform: uppercase;
}
packages/components/src/components/avatar/avatar.template.mjs
/**
 * Avatar — HTML template for the web implementation.
 * Pure function of the spec properties (packages/components/specs/components/avatar.spec.json) -> HTML string.
 * A span with role="img" named by the person (aria-label), holding either the picture (an img with
 * an empty alt: the span already carries the name) or the initials derived from the name. When the
 * name is written next to the avatar, `decorative` hides the avatar instead of repeating it.
 */
import { escapeHtml, escapeAttr } from '../../lib/template-utils.mjs';

export const meta = { id: 'avatar', selector: '.ds-avatar' };

/** Up to two initials: first letters of the first and last words of the name. */
export function initialsOf(name) {
  const words = String(name ?? '').trim().split(/\s+/).filter(Boolean);
  if (!words.length) return '';
  const first = words[0][0] ?? '';
  const last = words.length > 1 ? words.at(-1)[0] ?? '' : '';
  return (first + last).toUpperCase();
}

export function render(props = {}) {
  const { name = 'Unknown', src = null, size = 'medium', shape = 'circle', decorative = false, state = null } = props;
  const attrs = ['class="ds-avatar"', `data-size="${escapeAttr(size)}"`, `data-shape="${escapeAttr(shape)}"`];
  if (decorative) attrs.push('aria-hidden="true"');
  else attrs.push('role="img"', `aria-label="${escapeAttr(name)}"`);
  if (state && state !== 'default') attrs.push(`data-state="${escapeAttr(state)}"`);
  const content = src
    ? `<img class="ds-avatar__image" data-part="image" src="${escapeAttr(src)}" alt="">`
    : `<span class="ds-avatar__initials" data-part="initials" aria-hidden="true">${escapeHtml(initialsOf(name))}</span>`;
  return `<span ${attrs.join(' ')}>${content}</span>`;
}

No other platform implementation recorded yet. Platform token outputs live under Platforms.