Siteplane UI

Avatar Group

Show several people as a row of overlapping avatars with an optional overflow count.

Markdown
MEBCSN+2 more members
"use client";

import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/siteplane/components/avatar";
import {
  AvatarGroup,
  AvatarGroupCount,
} from "@/siteplane/components/avatar-group";

const members = [
  {
    name: "Mara Ellis",
    initials: "ME",
    image: "https://ui.siteplane.io/avatars/mara.webp",
  },
  {
    name: "Ben Carter",
    initials: "BC",
    image: "https://ui.siteplane.io/avatars/ben.webp",
  },
  {
    name: "Sofia Nguyen",
    initials: "SN",
    image: "https://ui.siteplane.io/avatars/sofia.webp",
  },
];

export default function Example() {
  return (
    <AvatarGroup>
      {members.map((member) => (
        <Avatar key={member.name}>
          <AvatarImage alt={member.name} src={member.image} />
          <AvatarFallback>{member.initials}</AvatarFallback>
        </Avatar>
      ))}
      <AvatarGroupCount>
        +2<span className="sr-only"> more members</span>
      </AvatarGroupCount>
    </AvatarGroup>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/avatar-group

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/avatar @siteplane/card

Usage

import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/siteplane/components/avatar";
import {
  AvatarGroup,
  AvatarGroupCount,
} from "@/siteplane/components/avatar-group";

<AvatarGroup>
  <Avatar>
    <AvatarImage src="/avatars/mara-ellis.png" alt="Mara Ellis" />
    <AvatarFallback>ME</AvatarFallback>
  </Avatar>
  <Avatar>
    <AvatarImage src="/avatars/ben-carter.png" alt="Ben Carter" />
    <AvatarFallback>BC</AvatarFallback>
  </Avatar>
  <AvatarGroupCount>
    +2<span className="sr-only"> more members</span>
  </AvatarGroupCount>
</AvatarGroup>;

Composition

AvatarGroup
├── Avatar (one per person)
└── AvatarGroupCount (optional)

Put the avatars directly in the group, followed by an AvatarGroupCount for the people it does not show. Each item after the first overlaps the one before it by a quarter of its size, and later items sit on top of earlier ones. Every avatar and the count draw a 2 px ring in the color of the surface they sit on, so the circles stay apart on the page, on a Card or in a Dialog.

Sizes

Set size on the group, not on each avatar: sm 24 px, md 32 px (the default, the size of a single Avatar), lg 40 px or xl 48 px. The group sizes every avatar and the count, and the overlap scales with the size.

MEBCSN+9 more members
MEBCSN+9 more members
MEBCSN+9 more members
MEBCSN+9 more members
"use client";

import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/siteplane/components/avatar";
import {
  AvatarGroup,
  AvatarGroupCount,
} from "@/siteplane/components/avatar-group";

const sizes = ["sm", "md", "lg", "xl"] as const;

const members = [
  {
    name: "Mara Ellis",
    initials: "ME",
    image: "https://ui.siteplane.io/avatars/mara.webp",
  },
  {
    name: "Ben Carter",
    initials: "BC",
    image: "https://ui.siteplane.io/avatars/ben.webp",
  },
  {
    name: "Sofia Nguyen",
    initials: "SN",
    image: "https://ui.siteplane.io/avatars/sofia.webp",
  },
];

export default function Example() {
  return (
    <div className="flex flex-col items-center gap-4">
      {sizes.map((size) => (
        <AvatarGroup key={size} size={size}>
          {members.map((member) => (
            <Avatar key={member.name}>
              <AvatarImage alt={member.name} src={member.image} />
              <AvatarFallback>{member.initials}</AvatarFallback>
            </Avatar>
          ))}
          <AvatarGroupCount>
            +9<span className="sr-only"> more members</span>
          </AvatarGroupCount>
        </AvatarGroup>
      ))}
    </div>
  );
}

Large counts

Pass the number of people the group does not show as count. The count writes it as +12, and from 1,000 on in compact notation, such as +1.3K, with the exact number as its title; assistive technology reads the exact number, followed by the children, such as visually hidden text that says what it counts. A count never truncates: a longer one grows as a pill of the group's height, and a short one stays a circle. locale sets the locale that writes the number, en-US unless set, so the server and the browser render the same text.

MEBCSN+12 more members
MEBCSN+1,284 more members
"use client";

import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/siteplane/components/avatar";
import {
  AvatarGroup,
  AvatarGroupCount,
} from "@/siteplane/components/avatar-group";

const members = [
  {
    name: "Mara Ellis",
    initials: "ME",
    image: "https://ui.siteplane.io/avatars/mara.webp",
  },
  {
    name: "Ben Carter",
    initials: "BC",
    image: "https://ui.siteplane.io/avatars/ben.webp",
  },
  {
    name: "Sofia Nguyen",
    initials: "SN",
    image: "https://ui.siteplane.io/avatars/sofia.webp",
  },
];

export default function Example() {
  return (
    <div className="grid gap-4">
      {[12, 1284].map((count) => (
        <AvatarGroup key={count}>
          {members.map((member) => (
            <Avatar key={member.name}>
              <AvatarImage alt={member.name} src={member.image} />
              <AvatarFallback>{member.initials}</AvatarFallback>
            </Avatar>
          ))}
          <AvatarGroupCount count={count}>
            <span className="sr-only"> more members</span>
          </AvatarGroupCount>
        </AvatarGroup>
      ))}
    </div>
  );
}

On a card

The ring takes the color of the Card, so the group needs no border of its own. A small group fits in the CardAction beside a title, for example the reviewers of a task.

Design review
Due Friday
LHJWAB+4 more reviewers
"use client";

import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/siteplane/components/avatar";
import {
  AvatarGroup,
  AvatarGroupCount,
} from "@/siteplane/components/avatar-group";
import {
  Card,
  CardAction,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/siteplane/components/card";

const reviewers = [
  {
    name: "Lea Hartmann",
    initials: "LH",
    image: "https://ui.siteplane.io/avatars/lea.webp",
  },
  {
    name: "Jonas Weber",
    initials: "JW",
    image: "https://ui.siteplane.io/avatars/jonas.webp",
  },
  {
    name: "Anna Brooks",
    initials: "AB",
    image: "https://ui.siteplane.io/avatars/anna.webp",
  },
];

export default function Example() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle>Design review</CardTitle>
        <CardDescription>Due Friday</CardDescription>
        <CardAction>
          <AvatarGroup size="sm">
            {reviewers.map((reviewer) => (
              <Avatar key={reviewer.name}>
                <AvatarImage alt={reviewer.name} src={reviewer.image} />
                <AvatarFallback>{reviewer.initials}</AvatarFallback>
              </Avatar>
            ))}
            <AvatarGroupCount>
              +4<span className="sr-only"> more reviewers</span>
            </AvatarGroupCount>
          </AvatarGroup>
        </CardAction>
      </CardHeader>
    </Card>
  );
}

API reference

Component properties
PropTypeDefaultDescription
AvatarGroup.sizesm | md | lg | xlmdSize of every avatar and the count: 24, 32, 40 or 48 px. The overlap is a quarter of it.
AvatarGroup.renderReactElement | render functiondivComposes the group onto another element.
AvatarGroup.classNamestringunsetClasses for the row.
AvatarGroupCount.countnumberunsetThe number of people the group does not show, written as +N and from 1,000 on in compact notation (+1.3K), with the exact number for assistive technology and as the title.
AvatarGroupCount.localestringen-USThe locale that writes count.
AvatarGroupCount.childrenReactNodeunsetThe count as text, such as +2, or the words after count; give the count an accessible name that says what it counts.
AvatarGroupCount.renderReactElement | render functionspanComposes the count onto another element, such as a link to the full list.

AvatarGroup renders a div with data-slot="avatar-group" and accepts native div props and refs. AvatarGroupCount renders a span with data-slot="avatar-group-count" and accepts the props of Badge except variant, size, shape, profile and light, which the group sets. With count, the visible figure is a span with data-slot="avatar-group-count-value". The module exports AvatarGroupProps, AvatarGroupSize and AvatarGroupCountProps.

Read the installed source.

Usage notes

The application decides how many avatars to show and what the count says. Give each avatar a name: an image needs a meaningful alt, and an avatar that shows only initials can take role="img" and an aria-label. Give the count an accessible name that says what it counts, such as visually hidden text after the number.