Avatar Stack
betaOverlapping faces for a count of people, one initial each until photos load, with +N past the limit.
Preview
going
all fit
size
arrive
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. 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
| Prop | Type | Default | What it does |
|---|---|---|---|
| people | { person: string; photo?: string }[] | - | Required. Everyone in the count, in the order given. |
| size | "sm" | "md" | "lg" | "md" | Face size, as Avatar's: 21, 28, or 36px at the default density. |
| max | number | 5 | Faces shown before +N. |
| arrive | boolean | false | Faces added after the first paint pop in. The rest stay put. |
| aria-label | string | - | 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. |
| className | string | - | Extra classes. Set [--avatar-stack-ring:var(--background)] when the stack sits on the page, not a card. |
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>
);
}