# Avatar Group (/docs/display/avatar-group)



<ComponentShowcase name="avatar-group" />

## Installation [#installation]

```bash
npx shadcn@latest add https://ui.zyeon.ai/r/avatar-group.json
```

## Prompt [#prompt]

The prompt behind this component — paste it into your AI assistant to recreate or adapt it.

```text
Build a React + TypeScript + Tailwind "AvatarGroup" component (no runtime deps
beyond the cn() class-merge helper).

Contract
- export interface AvatarGroupItem { src?: string; alt: string; fallback?: string }
  — alt is required and always reaches the rendered <img>; fallback defaults to
  the first letter of alt, uppercased.
- export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement>
  { items: AvatarGroupItem[]; max?: number; size?: "sm" | "md" | "lg" }.
- Defaults: max = 5, size = "md". forwardRef to the root div, merge className
  via cn(), spread the remaining props on the root.
- Render the first max items; when items.length > max, append a "+N" chip with
  N = items.length - max — always computed from the data, never hardcoded.

Behavior
- Overlap: the root is a flex row with a per-size negative gap (-space-x-1.5 /
  -space-x-2 / -space-x-2.5 for sm / md / lg); later siblings naturally paint
  on top of earlier ones.
- Hover reveal: hovering one avatar raises it above its neighbors (hover:z-10)
  and lifts it slightly (hover:-translate-y-1, transition-transform ~200ms).
  Under prefers-reduced-motion the transition and lift are disabled
  (motion-reduce:transition-none motion-reduce:hover:translate-y-0); the z
  raise stays because it is a functional reveal, not decoration.
- Image fallback: each avatar is a tiny stateful subcomponent with a `failed`
  flag; when src is missing or the img fires onError, swap the <img> for a
  letter layer showing fallback ?? alt[0]. A broken-image glyph never shows.
- Accessibility: the <img> carries the real alt; the letter layer carries
  role="img" aria-label={alt}, so every avatar keeps its accessible name either
  way. The "+N" chip appends a visually-hidden " more" so screen readers hear
  "+2 more". Nothing is focusable — the stack is purely presentational.
- No built-in tooltip and no click handlers; consumers compose those around
  the component (see levers).

Rendering & styling
- Semantic tokens only. Every circle wears ring-2 ring-background — the ring
  matches the page background and carves the cutout seam between overlapping
  avatars, in light and dark mode alike. Letter fallbacks and the "+N" chip
  use bg-muted text-muted-foreground. No hex values, no palette classes.
- Circles: rounded-full overflow-hidden shrink-0 select-none; the img is
  size-full object-cover. Sizes sm / md / lg map to size-6 / size-8 / size-10
  with text sizes text-[10px] / text-xs / text-sm.
- Keep the size → { avatar classes, overlap class } mapping in one const
  object so adding a size is a one-line change.

Customization levers
- Overlap density: the per-size -space-x-* values are the single overlap knob —
  -space-x-3 on lg reads as a dense facepile, -space-x-1 as a loose row.
- Stacking direction: by default later avatars paint on top; to put the first
  member on top instead, add flex-row-reverse -space-x-reverse justify-end to
  the root className and reverse the items array — visual order is preserved,
  paint order inverts.
- Tooltip with names on hover: wrap the stack in shadcn <TooltipProvider> and,
  inside the item loop, wrap each avatar in <Tooltip><TooltipTrigger asChild>
  …</TooltipTrigger><TooltipContent>{item.alt}</TooltipContent></Tooltip> —
  the alt string already carries the display name.
- Click-to-expand roster: turn the "+N" chip into a <button> that opens a
  shadcn Popover or DropdownMenu listing items.slice(max) with full names;
  keep the individual avatars non-interactive.
- Extra size: add one entry to the size map (e.g. xl → size-12 text-base with
  -space-x-3); nothing else changes.
- Ring width: ring-2 is tuned for size-6…10 circles; bump toward ring-[3px]
  for size-12 and up so the seam keeps its proportion.
```

## Concepts [#concepts]

<Mermaid
  chart="`flowchart TD
A[&#x22;items array&#x22;] --> B{&#x22;count > max ?&#x22;}
B -->|&#x22;no&#x22;| C[&#x22;render all avatars&#x22;]
B -->|&#x22;yes&#x22;| D[&#x22;first max avatars&#x22;]
D --> E[&#x22;overflow chip<br/>'+N' = count - max&#x22;]
C --> F[&#x22;overlap stack<br/>(negative space, later on top)&#x22;]
D --> F
F --> G[&#x22;ring cutout<br/>(ring matches background)&#x22;]
F --> H[&#x22;hover reveal<br/>(z raise + slight lift)&#x22;]
A --> I{&#x22;src loads ?&#x22;}
I -->|&#x22;yes&#x22;| J[&#x22;img with real alt&#x22;]
I -->|&#x22;no or onError&#x22;| K[&#x22;letter fallback<br/>(aria-label keeps the name)&#x22;]`"
/>

* **Ring cutout** — each circle wears a ring in the page-background color, so the overlap seam looks carved without any mask tricks, and it recolors itself in dark mode for free.
* **Computed overflow** — the "+N" chip is derived from `items.length - max` at render time; the count can never drift from the data it summarizes.
* **Hover reveal** — a partially hidden avatar becomes fully visible through a z-index raise; the slight lift is decorative and turns off under `prefers-reduced-motion`, while the reveal itself stays functional.
* **Graceful image degradation** — a missing or failing `src` swaps the image for an initials layer; the accessible name (`alt`) survives the swap and a broken-image glyph never renders.
* **Glance, not roster** — a facepile answers "who is on this?" in one glance; managing members (names, roles, actions) belongs to a list or table, not to this stack.
