Data Display

Avatar Stack

A row of overlapping circular avatars; on hover or focus the avatar lifts and scales up, its neighbours move aside, and a name tooltip floats out.

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add avatar-stack.en

New project or installation trouble? Start here →

Avatar Stack is a React avatar group component built with Motion (motion/react) and Tailwind CSS. Avatars overlap left over right; hovering or keyboard-focusing one lifts and enlarges it while its neighbours move aside, and its name appears above. People beyond max collapse into a +N bubble, and avatars without an image get a stable gradient and initial derived from the name.

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

5
40
Active edited usageDefault props
<AvatarStack/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

When to use

  • Showing who is currently viewing a document in a collaboration tool
  • Listing assignees on a project card or task row
  • An event or course page showing "N people registered" with a few faces
  • Social proof in a homepage hero next to copy such as "used by 10,000+ teams"
  • Not a good fit: when you need to show full names, titles and photos for each person, use Team Grid Section

Installation

npx webberui@latest add https://webberui.com/r/avatar-stack.en.json

Or, once registries are configured in components.json, install it as @webberui/avatar-stack.

Usage

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

<AvatarStack
  avatars={[
    { src: "/team/ada.jpg", alt: "Ada Lovelace" },
    { alt: "Alan Turing" },
    { alt: "Grace Hopper", fallback: "GH" },
  ]}
  max={5}
  size={40}
/>;

Examples

Text avatars from names

Without src, the first character of alt becomes the avatar text. The background is computed from the name, so the same person always gets the same color.

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

<AvatarStack
  avatars={[
    { alt: "Maya Chen" },
    { alt: "Liam Ortiz" },
    { alt: "Sofia Rossi" },
    { alt: "Noah Kim" },
  ]}
  size={36}
/>;

Collaborators on a document card

A smaller size and a low max fit a card corner; the rest show up as +N.

<div className="flex items-center justify-between rounded-xl border p-4">
  <div>
    <p className="text-sm font-medium">2026 Q4 product plan</p>
    <p className="text-xs text-neutral-500">Updated 10 minutes ago</p>
  </div>
  <AvatarStack avatars={editors} max={3} size={28} />
</div>

Map API data

Convert user records from your backend into AvatarStackItem; the type can be imported directly.

import { AvatarStack, type AvatarStackItem } from "@/components/ui/avatar-stack";

type Member = { id: string; name: string; avatarUrl: string | null };

function MemberAvatars({ members }: { members: Member[] }) {
  const avatars: AvatarStackItem[] = members.map((m) => ({
    src: m.avatarUrl ?? undefined,
    alt: m.name,
  }));
  return <AvatarStack avatars={avatars} max={4} />;
}

Props

PropTypeDefaultDescription
avatarsAvatarStackItem[]—The avatars, stacked left to right in order (left on top of right)
maxnumber5How many avatars to show at most; the rest are collapsed into a +N bubble
sizenumber40Avatar diameter (px)
classNamestring—Custom styles for the outer container

AvatarStackItem

FieldTypeDescription
srcstring?Avatar image URL; when omitted, the text fallback is shown
altstringAlternative text, also used as the name shown in the tooltip
fallbackstring?Character shown when there is no image; when omitted, the first character of alt is used

How it works

  • Avatars overlap via negative margins, with the left one on top of the right one (z-index decreases from left to right).
  • On hover or keyboard focus of any avatar, that avatar lifts and scales up and takes the topmost layer, its left and right neighbours move aside, and a name tooltip floats out above it.
  • With no image, a stable gradient background is generated from the name and the first character is shown; the same name always produces the same result.
  • Avatars beyond max are collapsed into a +N bubble that follows the visible avatars.

Accessibility

  • Each avatar is a button with an aria-label (the name), can be focused with Tab, and shows the tooltip on focus just as it does on hover.
  • The tooltip is purely visual (aria-hidden); the name is already exposed to assistive technology through aria-label, which avoids it being announced twice.
  • The +N bubble carries role="img" and an aria-label (such as “remaining 2 member”, "2 more members").
  • When the user has "reduce motion" enabled at the system level, the lift, the scale-up, and the offsetting are disabled; the tooltip still appears.

FAQ

Does it work with the Next.js App Router?

Yes. The file already starts with "use client", so import it from a Server Component page as usual and pass server-fetched data in as props.

I passed two letters as fallback; why is only one shown?

Text avatars show a single character: the first character of fallback (or alt if there is none), uppercased. If you need a two-letter monogram, provide an image instead.

Does it use next/image?

No, it renders a plain <img> with object-cover inside the circle. The image comes from your URL, so point it at an already resized version if you want smaller files.

How does it work with keyboards and screen readers?

Each avatar is a button with an aria-label, reachable with Tab; focusing one shows the name just like hover. The +N bubble announces how many more members there are. With the OS "reduce motion" setting on, the lift and shift are disabled.

What are the dependencies?

motion, clsx and tailwind-merge. You can also copy the source from the Manual tab in the installation section.