Skip to content
txbrendan/ui

Avatar Stack

beta

Overlapping faces for a count of people, one initial each until photos load, with +N past the limit.

Preview

going

14 going

all fit

size

arrive

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/avatar-stack

Use it for

  • Putting faces on a count of people: who is going, who replied, who has seen it.
  • Pass everyone in people, in order. max sets how many faces show before +N.
  • Set arrive where people join while the page is open, like check-ins. New faces pop in. The ones already there stay put.
  • Name everyone in full somewhere people can reach, like a peek on the count.

Avoid it for

  • One person. Use Avatar or Person.
  • A list people must read name by name. Use a list of Person rows.
  • Faces with no count or label near them. Say what the group is, like 21 going.

Props

  • people

    Required. Everyone in the count, in the order given.

    { person: string; photo?: string }[]

  • size

    Face size, as Avatar's: 21, 28, or 36px at the default density.

    "sm" | "md" | "lg" · default "md"

  • max

    Faces shown before +N.

    number · default 5

  • arrive

    Faces added after the first paint pop in. The rest stay put.

    boolean · default false

  • aria-label

    The stack's name for screen readers. Defaults to up to three names and how many more, like Sam Carter, Kate Clarke, Josh Tait and 18 more.

    string

  • className

    Extra classes. Set [--avatar-stack-ring:var(--background)] when the stack sits on the page, not a card.

    string

Accessibility

  • Renders one <span role="img">. Its name lists up to three people and how many more, so screen readers hear one short line, not every face.
  • Pass aria-label to say what the group is, like 21 going.
  • The faces and the +N are hidden from screen readers. Name everyone in full where people can reach them.
  • The pop only grows and fades. Under reduced motion it only fades.

Examples

Who is going

<AvatarStack
  people={[{ person: "Sam Carter" }, { person: "Kate Clarke" }, { person: "Josh Tait" }, { person: "Mia Okafor" }, { person: "Tom Hale" }, { person: "Priya Shah" }]}
  max={4}
  aria-label="6 going"
/>

Check-ins that pop in

"use client";

import { useState } from "react";
import { AvatarStack } from "@/components/ui/avatar-stack";

export function CheckIns() {
  const [people, setPeople] = useState([{ person: "Sam Carter" }]);
  return (
    <div className="flex items-center gap-3">
      <AvatarStack people={people} arrive aria-label={`${people.length} checked in`} />
      <button type="button" onClick={() => setPeople((all) => [...all, { person: `Guest ${all.length}` }])}>
        Check in
      </button>
    </div>
  );
}