Designer view
Avatar Group
A composition of avatar instances rendered as a stack or grid with bounded count and an overflow indicator (+N) for collaborator lists, attribution rows, and shared-document headers. Codifies the canonical overflow contract — counts beyond `max` collapse into a single indicator tile — and the disclosure pattern for revealing the full member list.
Also called Avatar stack Stacked avatars User stack Member list
When to use
Use
For representing a group of users, organisations, or entities in a bounded surface — collaborator strips on shared documents, member rows in team headers, attribution lines on commits, and viewer lists in real-time editors. Pair with a textual count or member- list affordance when the overflow indicator is the only access path to the collapsed members. Stack layout suits dense inline contexts (header strips, attribution lines); grid layout suits member-management surfaces (settings pages, organisation rosters).
Avoid
For a single-entity representation — that is `Avatar`, not a group of one. For a long member list with metadata (presence, role, activity timestamp) — that is a list of `ListItem` rows with a leading avatar slot, not a stack. For unbounded counts on dense surfaces — set a sensible `max` (typically 3-5) so the visual weight stays predictable. For decorative groupings without identity meaning — that is layered tiles, not avatars.
Versus related
- avatar
`Avatar` is the single-entity representation primitive that Avatar Group composes; Avatar Group is the bounded set representation. A single Avatar is "this person"; an Avatar Group is "this group of people". When the count is one, render Avatar directly — Avatar Group with one child is an anti-pattern.
- list-item
`ListItem` is a one-dimensional row with leading avatar + primary text + secondary text + trailing affordances; Avatar Group is a bounded visual unit that collapses identity into a compact strip. ListItem suits member-management with metadata (presence, role, activity); Avatar Group suits inline density where the group is a single visual marker. The decision test: does each member need its own row of metadata (ListItem) or can the group be summarised as a single strip (Avatar Group)?
Avatar Group composes multiple Avatar instances into a single visual unit when a surface needs to communicate "this is a group" rather than "this is a person". Two layouts (stack-overlapped or grid-tiled), a bounded `max` count that triggers a +N overflow indicator, and an optional disclosure that reveals the collapsed members on click. The reference documents the group-role + accessible-name contract, the overflow-tile interactivity rule (button when it opens a popover, static text otherwise), and the divergence between libraries that ship a single AvatarGroup (MUI, Atlassian, HeroUI) and libraries that defer overflow to manual composition (Mantine, Chakra, PrimeReact).
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
container | frame | Auto-layout frame; horizontal direction for stack variant, wrap-grid for grid variant; negative item-spacing for stack-overlap effect |
avatar | instance | Avatar component instance; repeats per max property; z-index increments to layer correctly |
overflow-indicator | frame | Avatar-shaped frame with "+N" centred text; visibility per "has overflow" property |
overflow-popover | frame | Floating panel anchored below the overflow indicator; vertical list of member rows; popover-style elevation |
Token usage per slot
container- spacing
- gap
spacing.tight
- gap
- color
- background
color.surface.bg
- background
avatar- radius
- corner
radius.full
- corner
overflow-indicator- radius
- corner
radius.full
- corner
- color
- background
color.surface.sunken - foreground
color.text.muted
- background
- typography
- size
text.sm - weight
weight.semibold
- size
overflow-popover- radius
- corner
radius.md
- corner
- color
- background
color.surface.raised - border
color.border.subtle
- background
- elevation
- depth
elevation.overlay
- depth
Figma ↔ Code property map
| Figma | Kind | Code | Notes |
|---|---|---|---|
Variant | Enum | variant | Maps stack / grid layout variant. |
Size | Enum | size | Maps xs / sm / md / lg / xl; mirrors Avatar size. |
Overflow Direction | Enum | overflowDirection | end (default, last DOM avatar on top) or start (first DOM avatar on top). |
Interactive | Boolean | interactive | When true, overflow indicator renders as `<button>` and opens a popover; when false, indicator is a labelled `<span>`. |
Max | Number | max | Bound on visible avatar count; counts beyond render as +N indicator. |
Total | Number | total | Optional explicit total; when present, overrides children length for surplus computation. |
Avatars | Slot | avatar | Repeatable slot for child Avatar instances. |
Overflow Popover | Slot | overflow-popover | Optional disclosure surface; required when interactive is true. |
Motion
| Transition | Duration token |
|---|---|
popoverOpen | motion.duration.fast |
hoverShift | motion.duration.fast |
Responsive behaviour
| Breakpoint | Change |
|---|---|
breakpoint.sm | Below this width, dense stacks step the avatar size down one tier (md → sm) and reduce the visible `max` by one or two so the strip fits without horizontal overflow. Grid variants reflow to fewer columns. Overflow-indicator always remains visible when total exceeds the reduced max. |
breakpoint.md | At and above this width, the canonical max applies and the size token returns to the explicit size. Grid variants return to their authored column count. |
Internationalisation
RTL · mirroring
Stack overlap direction follows `margin-inline-start` and the `overflowDirection` enum is logical — `end` flips from right-side-on-top (LTR) to left-side-on-top (RTL) automatically. Grid variant tile order also mirrors via writing-direction-aware grid auto-flow. Overflow indicator sits at the inline-end of the stack regardless of language — it follows the direction of growth, not a fixed visual corner.
Text expansion
Overflow indicator text "+5" stays compact across languages. The accessible-name suffix ("more members" → German "weitere Mitglieder" 60 % longer; Russian "еще участников" 80 % longer) expands invisibly to AT but never affects layout. Group `aria-label` ("Document collaborators") follows generic prose expansion — reserve no extra width because the label is not visible.
Variants, properties, states
Variants
Structurally different versions of the component.
stack grid Properties
The same component, parameterised.
| Property | Type |
|---|---|
size | xs | sm | md | lg | xl |
overflowDirection | end | start |
interactive | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visible |
data | emptypartialoverflowingexpanded |
State transitions
| From | To | Trigger |
|---|---|---|
overflowing | expanded | User activates overflow indicator (click, Enter, Space) |
expanded | overflowing | User dismisses popover (Escape, outside click, indicator re-click) |
Figma↔Code mismatches
- 01 Figma
Avatars laid out with positive item-spacing (no overlap)
CodeStack variant uses negative `margin-inline-start` to overlap avatars
ConsequenceDesigners ship the stack variant with non-overlapping avatars (positive gap); developers implement the canonical overlap with negative margins. The two surfaces look like different layouts even though they intend the same. Designers find the overlap "broken"; developers find the spec "underspecified".
CorrectDocument the stack variant as overlapping by canonical contract (negative inline-start margin per avatar after the first; offset bound to size token). Grid variant uses positive gap and no overlap. Figma component variants enforce both layouts as first-class.
- 02 Figma
Overflow indicator drawn as static text "+5"
CodeOverflow indicator rendered as `<button>` with `aria-label`
ConsequenceDesigners ship "+5" as a text label; developers ship a button with `aria-label="5 more members"`. The visual element looks static but behaves as a focusable, clickable target. Designers do not draw the focus ring or hover state; developers ship them anyway, leading to design / implementation drift on focus affordance.
CorrectDocument the overflow indicator as `<button>` whenever the group is interactive (opens a popover). Figma carries the button-state variants (default / hover / focused / active) so the focus ring is part of the design. Static overflow (no popover) is the rare case — document it separately as a labelled `<span>`.
- 03 Figma
Stack overlap direction hard-coded as left-overlaps-right
CodeOverlap direction follows `inset-inline-start` (logical, RTL-aware)
ConsequenceDesigners ship a fixed overlap direction (left avatar on top); developers use logical inline properties that flip under RTL. In RTL contexts the LTR Figma file ships with the reverse visual order, but the logical-property code agrees with the RTL reading direction. The two surfaces visually disagree.
CorrectDocument the overlap direction as logical — `overflowDirection` `end` means the last DOM-order avatar sits on top in LTR and on the bottom in RTL. Figma shows both LTR and RTL frames; code uses logical properties uniformly.
- 04 Figma
Max count baked into Figma as a fixed-instance count
Code`max` prop drives the runtime bound; total count comes from `total` prop or children length
ConsequenceDesigners ship a 5-avatar variant frame; developers compute visible count from `min(total, max)` and render the indicator from the surplus. The two surfaces decouple — designers may not draw the overflow-indicator state for every avatar count they author.
CorrectDocument `max` and `total` as canonical properties; Figma illustrates the three data states (partial / overflowing / expanded) for at least one representative count. Code computes visible avatars and surplus from those props at render time.
Contracts
Non-negotiable contracts
APGWAI-ARIA role=group plus accessible-name pattern The container carries `role="group"` plus `aria-label` describing the group. Without either, SR users hear a sequence of individual avatar names without the "this is a group" semantic. The label should include the total member count when the overflow indicator does not surface it independently.
Without the role and label, the group is a sighted-only composition. AT users encounter ambiguous avatar sequences — the membership boundary, the count, and the group's purpose all collapse into a flat list of names.
Canon Counts beyond `max` collapse into a single overflow indicator with the literal surplus ("+N"). The indicator is non-optional whenever total count > `max`. Implementations that hide surplus avatars without the indicator communicate a smaller membership than reality.
Without the indicator, users perceive the group as smaller than it is. The collapsed members lose representation; the membership count becomes lossy.
APGAPG: Disclosure pattern The overflow indicator is `<button>` whenever it opens a popover (interactive group). It carries `aria-expanded` reflecting the popover state and `aria-controls` referencing the popover. Static groups (no popover) render the indicator as a labelled `<span>`. Implementations that ship a `<div>` with onClick lose keyboard activation, focus order, and disclosure semantics.
Without the button semantic, keyboard users cannot reach the popover. AT users cannot perceive that the indicator is interactive — `aria-expanded` is the canonical signal for "this control toggles disclosed content".
Vocabulary drift
- Material 3
Avatar- Material 3 spec defines Avatar but not AvatarGroup as a first-class component; library implementations (Material UI) ship AvatarGroup as a composition utility on top of Avatar. Same canonical contract, different documentation surface.
- Atlassian
Avatar Group- Atlassian explicitly supports two layouts (stack or grid) as first-class variants — the rationale for the canonical variant axis. Most other libraries default to stack only.
- Polaris
Avatar- Polaris ships Avatar but does not ship a canonical AvatarGroup — composition is left to the consumer. The canon documents this absence as an industry-divergence point, not a gap in the canon itself.
- Carbon
User Avatar- Carbon ships User Avatar but no AvatarGroup — same pattern as Polaris. Consumers compose manually with custom layouts.
- GOV.UK
—- GOV.UK Design System does not spec Avatar or AvatarGroup at all — government services rarely surface user-identity strips. The canon documents Avatar Group for the productivity-app context where the pattern is dominant.
Common mistakes
#avatar-group-no-group-role
Container has no `role="group"` or `aria-label`
The container is a bare `<div>` with no role and no accessible name. SR users hear a sequence of individual avatar names without group context — "Alex Black image, Jamie Lee image, Sam Reyes image" — losing the "this is a collaborator strip" semantic entirely.
Set `role="group"` on the container plus `aria-label` describing the group ("Document collaborators (5)", "Team A members"). The label should include the total member count when the overflow indicator does not surface it. Avoid `role="list"` — list semantics are for sequential enumeration, not a bounded set.
#avatar-group-overflow-no-aria-label
Overflow indicator shows "+N" with no accessible label
The indicator renders `<span>+5</span>` with no `aria-label`, no visually-hidden suffix, and no associated description. SR users hear "five" or "plus five" with no semantic — the count is ambiguous (more avatars? more members? more icons?).
When interactive, `<button aria-label="5 more members">+5</ button>` (or "+5 more"). When static, append a visually-hidden suffix — `<span>+5<span class="sr-only"> more members</span> </span>`. Never rely on the literal "+N" — the plus sign is visual shorthand without context for AT.
#avatar-group-overflow-not-button-when-interactive
Overflow indicator is `<div>` or `<span>` but opens a popover
The indicator opens a member-list popover when clicked but ships as a non-button element (often a div with onClick). The element is not in the keyboard tab order, has no `aria-expanded`, and offers no Enter / Space activation. The popover is mouse-only.
Render the interactive overflow indicator as `<button>` with `aria-expanded` reflecting the popover state and `aria-controls` referencing the popover id. Static (no popover) overflow uses a `<span>`. The interactive boolean property toggles the rendered semantic — never ship a div that behaves like a button.
#avatar-group-counts-beyond-max-silent-collapse
Counts beyond `max` collapse without the overflow indicator
The implementation hides avatars beyond `max` but does not render the +N indicator. Users see five collaborators in a ten-collaborator group with no signal that more exist. The group communicates a smaller membership than reality.
Whenever total count > `max`, render the overflow indicator with the literal surplus ("+N" where N = total - max). The indicator is non-optional in the overflowing state; only the partial state (total ≤ max) omits it.
#avatar-group-popover-no-focus-trap
Popover opens but focus does not move into it
The disclosure popover opens on indicator click but keyboard focus stays on the indicator. Tab does not enter the popover; Escape does nothing. Keyboard users cannot navigate the member list — the popover is mouse-only despite the indicator being a button.
On open, move focus into the popover (first focusable element, or the popover container itself with `tabindex="-1"` for read-only lists). Escape closes the popover and returns focus to the indicator. Outside-click closes without focus restoration unless the click landed on another focusable element.
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
container | Carries `role="group"` plus `aria-label` describing the group ("Document collaborators (5)", "Team A members"). Without the label, SR users hear a sequence of individual avatar names without group context. The accessible name should include the total member count when the overflow indicator does not announce it. | |
avatar | Each child Avatar carries its own accessible name (per the Avatar canon — image variant via `<img alt>`, initials / icon variants via `role="img"` + `aria-label`). The container's `role="group"` collects them; SR announces "group, Alex Black, Jamie Lee, Sam Reyes, +4 more". | |
overflow-indicator | When interactive, `<button>` with `aria-label="N more members"` (or "+N more") plus `aria-expanded` reflecting the popover state. When static, `<span>` with the visible "+N" text and a visually-hidden "more members" suffix. Never rely on the literal "+N" alone — SR users without context cannot decode the plus sign. | |
overflow-popover | The popover follows the disclosure pattern (APG): the overflow-indicator carries `aria-expanded` plus `aria-controls` referencing the popover; focus moves into the popover on open; Escape closes and returns focus to the indicator. When the popover is a list of selectable members, use `role="listbox"` with arrow-key navigation; when it is a read-only overview, use a generic container with a heading. |