Deck
Browse the catalog

Build / Molecules

AvatarGroup

  • molecule
  • play-fn

A handful of people as overlapping faces — who was there, in the space a sentence naming two of them would take.

The overlap is the point: a stack reads as "a group" at a glance where a row of separate avatars reads as a list to be counted. Each face keeps a ring in the surface behind it so the discs stay distinct where they cross; on a surface that isn't the page — a cover photo, a tinted card — set --deck-avatar-group-ring to whatever is actually behind them.

Every face is announced by name rather than left as decoration with a title attribute nobody hears. Faces past max become a "+N" that says how many were left out, so the count is never only in the geometry.

import { AvatarGroup } from '@meetcard/deck'

Example

<AvatarGroup label="Cards exchanged" max={4} people={[{ name: 'Hannah Davis' }]} />

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

Five people, five faces — the stack still reads as individuals.

Default
Source
const Default = () => <AvatarGroup
    people={[
      { name: 'Hannah Davis' },
      { name: 'Marcus Lee' },
      { name: 'Priya Shah' },
      { name: 'Diego Romero' },
      { name: 'Lena Fox' },
    ]}
    label="Cards exchanged" />;

Past max the rest become a count. The number is text, so it is never only in the geometry of the stack.

With Overflow
Source
const WithOverflow = () => <AvatarGroup
    people={[
      { name: 'Hannah Davis' },
      { name: 'Marcus Lee' },
      { name: 'Priya Shah' },
      { name: 'Diego Romero' },
      { name: 'Lena Fox' },
    ]}
    label="Cards exchanged"
    max={3} />;

Bigger faces, for a page that is about the people rather than the row.

Large
Source
const Large = () => <AvatarGroup
    people={[
      { name: 'Hannah Davis' },
      { name: 'Marcus Lee' },
      { name: 'Priya Shah' },
      { name: 'Diego Romero' },
      { name: 'Lena Fox' },
    ]}
    label="Cards exchanged"
    size="md"
    max={4} />;

On a surface that isn't the page. The ring each face carries is whatever is behind the stack, so it is a variable rather than a fixed colour.

On A Tinted Surface
Source
const OnATintedSurface = () => <div
    style={{
      background: 'var(--deck-color-background-brand-subtle)',
      padding: 'var(--deck-space-16)',
      borderRadius: 'var(--deck-radius-xl)',
      // @ts-expect-error -- a custom property is not in CSSProperties
      '--deck-avatar-group-ring': 'var(--deck-color-background-brand-subtle)',
    }}>
    <AvatarGroup
        people={[
          { name: 'Hannah Davis' },
          { name: 'Marcus Lee' },
          { name: 'Priya Shah' },
          { name: 'Diego Romero' },
          { name: 'Lena Fox' },
        ]}
        label="Cards exchanged"
        max={4} />
</div>;

One person is still a group — the layout does not change shape.

One Person
Source
const OnePerson = () => <AvatarGroup people={people.slice(0, 1)} label="Cards exchanged" />;

Props

PropTypeDefaultDescription
labelrequiredstring—Accessible name for the group, e.g. "Exchanged cards at RevOps Summit".
peoplerequiredAvatarGroupPerson[]——
maxnumber5How many faces to show before the rest collapse into a "+N". Default 5 — past that a stack stops reading as people and starts reading as texture.
size'xs' | 'sm' | 'md' | 'lg' | 'xl''sm'—

Also accepts everything from Omit, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.