Accessibility pattern · Data display

Avatar and presence

An avatar standing alone is the only place a name appears, so it carries the name; beside the name it is decoration, and announcing it again only doubles the noise. Presence is a word as well as a dot of a different shape, and the +4 in a stack of faces is a button that opens the full list, not a tooltip.

WCAG criteria
6
Keyboard rules
4
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Website redesign

8 members · due 24 October

Latest updates

  • Meera Iyer moved Home page hero to Review.

  • Kabir Mehta fixed the focus order in the sign-up form.

  • Don't: alt="avatar"

    Heard as “avatar, image, Asha Rao”. It says what the picture is, not who.

  • Don't: the name twice

    Heard as “Asha Rao, image, Asha Rao”, with alt="Asha Rao" beside the name.

  • Do: alt="" beside the name

    Heard as “Asha Rao”. Alone, as in the Team stack, it carries the name instead.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabMoves to your profile button, then the +4 button; when a panel is open, the next Tab goes into it.
Enter or SpaceOn the profile or +4 button, opens or closes its panel; focus stays on the button.
Arrow Down or Arrow UpIn Your status, chooses Online, Away or Do not disturb; the dot and the word under your name change together.
EscapeCloses the open panel and returns focus to the button that opened it.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Focus reaches the profile buttonAsha Rao Online, button, collapsed
Focus reaches the +4 button+4 more members, button, collapsed
Reading the Team stackTeam, list, 4 items. Meera Iyer, image. Kabir Mehta, image. Priya Nair, image. Ravi Shah, image
Tab moves into the open member listMeera Iyer Design lead Online, link
Away is chosen in Your statusAway, radio button, checked, 2 of 3
Escape closes the panelAsha Rao Away, button, collapsed

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-avatar" data-ap-avatar>
  <div class="ap-avatar__top">
    <div>
      <h3 class="ap-avatar__name">Website redesign</h3>
      <p class="ap-avatar__sub">8 members · due 24 October</p>
    </div>
    <div class="ap-avatar__anchor">
      <button type="button" class="ap-avatar__me" aria-expanded="false" aria-controls="avatar-account" data-ap-toggle>
        <span class="ap-avatar__pic ap-avatar__pic--sm" aria-hidden="true"><svg class="ap-avatar__art ap-avatar__art--info" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M10.5 27c1.7-2 2.2-5.4 2.2-10.6C12.7 10.3 15.9 6 20 6s7.3 4.3 7.3 10.4c0 5.2.5 8.6 2.2 10.6-2.6 1-5.8 1.5-9.5 1.5s-6.9-.5-9.5-1.5ZM5.5 40c1.2-7.6 6.8-11.2 14.5-11.2S33.3 32.4 34.5 40Z"/><circle class="ap-avatar__art-bg" cx="20" cy="17" r="5.4"/></svg><span class="ap-avatar__dot" data-presence="online" data-ap-me-dot></span></span>
        <span class="ap-avatar__me-text"><span class="ap-avatar__me-name">Asha Rao</span> <span class="ap-avatar__me-status" data-ap-me-word>Online</span></span>
        <svg class="ap-avatar__chevron" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"/></svg>
      </button>
      <div class="ap-avatar__pop ap-avatar__pop--end" id="avatar-account" hidden>
        <fieldset class="ap-avatar__status">
          <legend class="ap-avatar__pop-name">Your status</legend>
          <label class="ap-avatar__choice">
            <input type="radio" name="avatar-status" value="online" checked />
            <span aria-hidden="true" class="ap-avatar__dot" data-presence="online"></span>
            <span>Online</span>
          </label>
          <label class="ap-avatar__choice">
            <input type="radio" name="avatar-status" value="away" />
            <span aria-hidden="true" class="ap-avatar__dot" data-presence="away"></span>
            <span>Away</span>
          </label>
          <label class="ap-avatar__choice">
            <input type="radio" name="avatar-status" value="busy" />
            <span aria-hidden="true" class="ap-avatar__dot" data-presence="busy"></span>
            <span>Do not disturb</span>
          </label>
        </fieldset>
      </div>
    </div>
  </div>

  <div class="ap-avatar__section">
    <h4 class="ap-avatar__label" id="avatar-team">Team</h4>
    <div class="ap-avatar__anchor ap-avatar__group">
      <ul class="ap-avatar__stack" aria-labelledby="avatar-team">
        <li><span class="ap-avatar__pic" role="img" aria-label="Meera Iyer"><svg class="ap-avatar__art ap-avatar__art--success" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M12.7 15.5c-.3-5.4 3-8.9 7.3-8.9s7.6 3.5 7.3 8.9c-.3 4.1-3.4 7.3-7.3 7.3s-7-3.2-7.3-7.3ZM5.5 40c1.2-8.4 6.8-12.6 14.5-12.6S33.3 31.6 34.5 40Z"/></svg></span></li>
        <li><span class="ap-avatar__pic" role="img" aria-label="Kabir Mehta"><span class="ap-avatar__initials" data-ap-initials="Kabir Mehta">KM</span></span></li>
        <li><span class="ap-avatar__pic" role="img" aria-label="Priya Nair"><svg class="ap-avatar__art ap-avatar__art--accent" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M10.5 27c1.7-2 2.2-5.4 2.2-10.6C12.7 10.3 15.9 6 20 6s7.3 4.3 7.3 10.4c0 5.2.5 8.6 2.2 10.6-2.6 1-5.8 1.5-9.5 1.5s-6.9-.5-9.5-1.5ZM5.5 40c1.2-7.6 6.8-11.2 14.5-11.2S33.3 32.4 34.5 40Z"/><circle class="ap-avatar__art-bg" cx="20" cy="17" r="5.4"/></svg></span></li>
        <li><span class="ap-avatar__pic" role="img" aria-label="Ravi Shah"><span class="ap-avatar__initials" data-ap-initials="Ravi Shah">RS</span></span></li>
      </ul>
      <button type="button" class="ap-avatar__more" aria-label="+4 more members" aria-expanded="false" aria-controls="avatar-members" data-ap-toggle data-ap-more>+4</button>
      <div class="ap-avatar__pop" id="avatar-members" hidden>
        <p class="ap-avatar__pop-name" id="avatar-members-name">All 8 members</p>
        <ul class="ap-avatar__people" aria-labelledby="avatar-members-name">
          <li>
            <a class="ap-avatar__person" href="#avatar-p-meera">
              <span class="ap-avatar__pic" aria-hidden="true"><svg class="ap-avatar__art ap-avatar__art--success" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M12.7 15.5c-.3-5.4 3-8.9 7.3-8.9s7.6 3.5 7.3 8.9c-.3 4.1-3.4 7.3-7.3 7.3s-7-3.2-7.3-7.3ZM5.5 40c1.2-8.4 6.8-12.6 14.5-12.6S33.3 31.6 34.5 40Z"/></svg><span class="ap-avatar__dot" data-presence="online"></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Meera Iyer</span> <span class="ap-avatar__role">Design lead</span></span>
              <span class="ap-avatar__presence">Online</span>
            </a>
          </li>
          <li>
            <a class="ap-avatar__person" href="#avatar-p-kabir">
              <span class="ap-avatar__pic" aria-hidden="true"><span class="ap-avatar__initials" data-ap-initials="Kabir Mehta">KM</span><span class="ap-avatar__dot" data-presence="away"></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Kabir Mehta</span> <span class="ap-avatar__role">Developer</span></span>
              <span class="ap-avatar__presence">Away</span>
            </a>
          </li>
          <li>
            <a class="ap-avatar__person" href="#avatar-p-priya">
              <span class="ap-avatar__pic" aria-hidden="true"><svg class="ap-avatar__art ap-avatar__art--accent" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M10.5 27c1.7-2 2.2-5.4 2.2-10.6C12.7 10.3 15.9 6 20 6s7.3 4.3 7.3 10.4c0 5.2.5 8.6 2.2 10.6-2.6 1-5.8 1.5-9.5 1.5s-6.9-.5-9.5-1.5ZM5.5 40c1.2-7.6 6.8-11.2 14.5-11.2S33.3 32.4 34.5 40Z"/><circle class="ap-avatar__art-bg" cx="20" cy="17" r="5.4"/></svg><span class="ap-avatar__dot" data-presence="online"></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Priya Nair</span> <span class="ap-avatar__role">Researcher</span></span>
              <span class="ap-avatar__presence">Online</span>
            </a>
          </li>
          <li>
            <a class="ap-avatar__person" href="#avatar-p-ravi">
              <span class="ap-avatar__pic" aria-hidden="true"><span class="ap-avatar__initials" data-ap-initials="Ravi Shah">RS</span><span class="ap-avatar__dot" data-presence="offline"></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Ravi Shah</span> <span class="ap-avatar__role">Developer</span></span>
              <span class="ap-avatar__presence">Offline</span>
            </a>
          </li>
          <li>
            <a class="ap-avatar__person" href="#avatar-p-asha">
              <span class="ap-avatar__pic" aria-hidden="true"><svg class="ap-avatar__art ap-avatar__art--info" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M10.5 27c1.7-2 2.2-5.4 2.2-10.6C12.7 10.3 15.9 6 20 6s7.3 4.3 7.3 10.4c0 5.2.5 8.6 2.2 10.6-2.6 1-5.8 1.5-9.5 1.5s-6.9-.5-9.5-1.5ZM5.5 40c1.2-7.6 6.8-11.2 14.5-11.2S33.3 32.4 34.5 40Z"/><circle class="ap-avatar__art-bg" cx="20" cy="17" r="5.4"/></svg><span class="ap-avatar__dot" data-presence="online" data-ap-me-dot></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Asha Rao</span> <span class="ap-avatar__role">Product manager · You</span></span>
              <span class="ap-avatar__presence" data-ap-me-word>Online</span>
            </a>
          </li>
          <li>
            <a class="ap-avatar__person" href="#avatar-p-arjun">
              <span class="ap-avatar__pic" aria-hidden="true"><span class="ap-avatar__initials" data-ap-initials="Arjun Das">AD</span><span class="ap-avatar__dot" data-presence="busy"></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Arjun Das</span> <span class="ap-avatar__role">Content designer</span></span>
              <span class="ap-avatar__presence">Do not disturb</span>
            </a>
          </li>
          <li>
            <a class="ap-avatar__person" href="#avatar-p-nisha">
              <span class="ap-avatar__pic" aria-hidden="true"><span class="ap-avatar__initials" data-ap-initials="Nisha Kapoor">NK</span><span class="ap-avatar__dot" data-presence="offline"></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Nisha Kapoor</span> <span class="ap-avatar__role">QA engineer</span></span>
              <span class="ap-avatar__presence">Offline</span>
            </a>
          </li>
          <li>
            <a class="ap-avatar__person" href="#avatar-p-farhan">
              <span class="ap-avatar__pic" aria-hidden="true"><svg class="ap-avatar__art ap-avatar__art--warning" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M12.7 15.5c-.3-5.4 3-8.9 7.3-8.9s7.6 3.5 7.3 8.9c-.3 4.1-3.4 7.3-7.3 7.3s-7-3.2-7.3-7.3ZM5.5 40c1.2-8.4 6.8-12.6 14.5-12.6S33.3 31.6 34.5 40Z"/></svg><span class="ap-avatar__dot" data-presence="online"></span></span>
              <span class="ap-avatar__who"><span class="ap-avatar__pname">Farhan Ali</span> <span class="ap-avatar__role">Developer</span></span>
              <span class="ap-avatar__presence">Online</span>
            </a>
          </li>
        </ul>
      </div>
    </div>
  </div>

  <div class="ap-avatar__section">
    <h4 class="ap-avatar__label">Latest updates</h4>
    <ul class="ap-avatar__updates">
      <li>
        <span class="ap-avatar__pic ap-avatar__pic--sm" aria-hidden="true"><svg class="ap-avatar__art ap-avatar__art--success" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M12.7 15.5c-.3-5.4 3-8.9 7.3-8.9s7.6 3.5 7.3 8.9c-.3 4.1-3.4 7.3-7.3 7.3s-7-3.2-7.3-7.3ZM5.5 40c1.2-8.4 6.8-12.6 14.5-12.6S33.3 31.6 34.5 40Z"/></svg></span>
        <p><strong>Meera Iyer</strong> moved Home page hero to Review.</p>
        <time class="ap-avatar__when" datetime="2026-10-05T09:50">10 min ago</time>
      </li>
      <li>
        <span class="ap-avatar__pic ap-avatar__pic--sm" aria-hidden="true"><span class="ap-avatar__initials" data-ap-initials="Kabir Mehta">KM</span></span>
        <p><strong>Kabir Mehta</strong> fixed the focus order in the sign-up form.</p>
        <time class="ap-avatar__when" datetime="2026-10-05T08:15">2 h ago</time>
      </li>
    </ul>
  </div>

  <ul class="ap-avatar__compare" aria-label="One avatar beside a name, marked up three ways">
    <li class="ap-avatar__case ap-avatar__case--dont">
      <span class="ap-avatar__sample" aria-hidden="true"><span class="ap-avatar__pic ap-avatar__pic--sm"><svg class="ap-avatar__art ap-avatar__art--info" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M10.5 27c1.7-2 2.2-5.4 2.2-10.6C12.7 10.3 15.9 6 20 6s7.3 4.3 7.3 10.4c0 5.2.5 8.6 2.2 10.6-2.6 1-5.8 1.5-9.5 1.5s-6.9-.5-9.5-1.5ZM5.5 40c1.2-7.6 6.8-11.2 14.5-11.2S33.3 32.4 34.5 40Z"/><circle class="ap-avatar__art-bg" cx="20" cy="17" r="5.4"/></svg></span><span>Asha Rao</span></span>
      <div>
        <p class="ap-avatar__verdict">Don't: <code>alt="avatar"</code></p>
        <p>Heard as “avatar, image, Asha Rao”. It says what the picture is, not who.</p>
      </div>
    </li>
    <li class="ap-avatar__case ap-avatar__case--dont">
      <span class="ap-avatar__sample" aria-hidden="true"><span class="ap-avatar__pic ap-avatar__pic--sm"><svg class="ap-avatar__art ap-avatar__art--info" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M10.5 27c1.7-2 2.2-5.4 2.2-10.6C12.7 10.3 15.9 6 20 6s7.3 4.3 7.3 10.4c0 5.2.5 8.6 2.2 10.6-2.6 1-5.8 1.5-9.5 1.5s-6.9-.5-9.5-1.5ZM5.5 40c1.2-7.6 6.8-11.2 14.5-11.2S33.3 32.4 34.5 40Z"/><circle class="ap-avatar__art-bg" cx="20" cy="17" r="5.4"/></svg></span><span>Asha Rao</span></span>
      <div>
        <p class="ap-avatar__verdict">Don't: the name twice</p>
        <p>Heard as “Asha Rao, image, Asha Rao”, with <code>alt="Asha Rao"</code> beside the name.</p>
      </div>
    </li>
    <li class="ap-avatar__case ap-avatar__case--do">
      <span class="ap-avatar__sample" aria-hidden="true"><span class="ap-avatar__pic ap-avatar__pic--sm"><svg class="ap-avatar__art ap-avatar__art--info" viewBox="0 0 40 40" aria-hidden="true" focusable="false"><rect class="ap-avatar__art-bg" width="40" height="40"/><path class="ap-avatar__art-fig" d="M10.5 27c1.7-2 2.2-5.4 2.2-10.6C12.7 10.3 15.9 6 20 6s7.3 4.3 7.3 10.4c0 5.2.5 8.6 2.2 10.6-2.6 1-5.8 1.5-9.5 1.5s-6.9-.5-9.5-1.5ZM5.5 40c1.2-7.6 6.8-11.2 14.5-11.2S33.3 32.4 34.5 40Z"/><circle class="ap-avatar__art-bg" cx="20" cy="17" r="5.4"/></svg></span><span>Asha Rao</span></span>
      <div>
        <p class="ap-avatar__verdict">Do: <code>alt=""</code> beside the name</p>
        <p>Heard as “Asha Rao”. Alone, as in the Team stack, it carries the name instead.</p>
      </div>
    </li>
  </ul>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.1.1 Non-text Content Level A

    A picture standing alone is named after the person; one beside the name is hidden, so the name is said once.

  • 1.4.1 Use of Color Level A

    Presence is a word, and each dot has its own shape: filled for Online, half for Away, a bar for Do not disturb, a ring for Offline.

  • 1.4.3 Contrast (Minimum) Level AA

    Initials sit on one of five token pairs that each clear 4.5:1 in light and dark, whichever tone a name gets.

  • 1.4.11 Non-text Contrast Level AA

    Presence dots clear 3:1 against the surface, with a ring in the surface color that separates them from the picture.

  • 2.5.3 Label in Name Level A

    The +4 button's name, +4 more members, starts with what it shows, so it can be said aloud to speech recognition.

  • 4.1.2 Name, Role, Value Level A

    Both buttons expose aria-expanded and point at their panels; the status choice is a native radio group.

Usage

When to use it

Use it

  • Showing who is on a team, who did something, or who is signed in, where a face or initials helps people scan.
  • Presence in tools people use together: chat, documents, project boards.

Use something else

  • The only way to find out who someone is: always show or offer the name too.
  • Long member lists: a stack of faces is a summary; give the full list as text.
  • Presence that changes every few seconds: announcing each change is noise; let people look it up.

Common failures

How it usually goes wrong

  • alt="avatar" or "profile picture"

    It names the kind of image, not the person. People hear "avatar" over and over and learn nothing.

  • The name said twice

    alt="Asha Rao" right beside the text Asha Rao doubles every row. Beside a visible name the image is decoration: alt="" or aria-hidden.

  • Presence as a colored dot

    Green, amber and grey dots look alike to many people. Here each status has a word and its own shape.

  • Initials on a random hue

    Generated colors give some people white text on yellow. Picking from a fixed set of passing token pairs keeps every name readable.

  • +4 that only shows a tooltip

    Hovering to see who else is there leaves out keyboard and touch users. The +4 here is a button that opens the full list.

  • A profile button with no name

    A round picture alone as the account button is announced as "button". The name sits in the button as text, with the picture hidden.

Notes

Building it

  • With real photos the rules are the same: <img alt="Meera Iyer"> when the picture stands alone, alt="" when the name is beside it. The demo uses role=img with aria-label on drawn pictures, which works the same way.
  • The script picks each initials tone from a hash of the name, so a person keeps their color; the five tones are token pairs, so contrast is checked once for all of them.
  • Both panels are disclosures, not menus: they hold links and radio buttons, so Tab and the arrow keys work as usual, and they close when focus leaves them.
  • Your own presence changes the dot and the word in one place each; other people's presence is read when someone looks, not announced as it changes.

Sources: W3C WAI Images Tutorial: Decorative images · WCAG 2.2 Understanding: Label in Name

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction