VayuUI

AvatarGroup

Displays a stack of avatars with overflow handling.

Installation

npx vayu-ui init
npx vayu-ui add avatargroup
npx vayu-ui add -t avatargroup

Usage

Basic Stack
Grid Layout (All Visible)
Sizes
Interactive (Click avatars to select)
With Overflow
Status Indicators
const users = [
  { id: 1, username: "John Doe", src: "https://github.com/shadcn.png", status: "online" as const },
  { id: 2, username: "Jane Smith", fallback: "JS", status: "offline" as const },
  { id: 3, username: "Bob Wilson", src: "https://github.com/vercel.png", status: "online" as const },
  { id: 4, username: "Alice Johnson", fallback: "AJ", status: "away" as const },
  { id: 5, username: "Charlie Brown", src: "https://github.com/octocat.png", status: "busy" as const },
  { id: 6, username: "David Lee", fallback: "DL", status: "offline" as const },
];

<AvatarGroup users={users} maxDisplay={4} />

<AvatarGroup users={users} layout="grid" maxDisplay={6} />

<AvatarGroup users={users.slice(0, 3)} size="small" spacing="tight" />
<AvatarGroup users={users.slice(0, 3)} size="medium" />
<AvatarGroup users={users.slice(0, 3)} size="large" spacing="loose" />
<AvatarGroup users={users.slice(0, 3)} size="xlarge" />

<AvatarGroup
  users={users}
  maxDisplay={5}
  onAvatarClick={(user, index) => console.log(user)}
  onOverflowClick={(hiddenUsers) => console.log(hiddenUsers)}
/>

Anatomy

<AvatarGroup>
  {/* Root container — role="group" */}
  <AvatarItem>
    {/* Per-user avatar wrapper with Avatar inside */}
    <Avatar>
      <Avatar.Image />
      <Avatar.Status />
    </Avatar>
  </AvatarItem>
  <AvatarItem>...</AvatarItem>
  <OverflowButton>{/* "+N" indicator shown when users exceed maxDisplay */}</OverflowButton>
</AvatarGroup>
PartDescription
RootFlex container with role="group" and keyboard handling
AvatarItemPer-user wrapper — renders Avatar with positioning
OverflowButtonButton showing remaining count when overflow occurs

Accessibility

Keyboard Support

KeyBehavior
ArrowRightMove focus to the next interactive avatar
ArrowLeftMove focus to the previous interactive avatar
TabMove focus into and out of the avatar group
EnterActivate the focused clickable avatar
SpaceActivate the focused clickable avatar

ARIA Attributes

ElementAttributeValue
Rootrolegroup
Rootaria-labelAvatar group with {count} members
Avatar (static)aria-label (from Avatar){username}'s avatar
Avatar (clickable)rolebutton
Avatar (clickable)tabIndex0
OverflowButtonrolebutton
OverflowButtonaria-labelShow {count} more users

Focus Behavior

  • Focus is managed via roving tabindex on interactive avatars (role="button")
  • focus-visible ring uses ring-focus design token with offset
  • All hover animations respect prefers-reduced-motion via motion-safe: prefix
  • Minimum touch target of 24x24px (WCAG 2.5.5)

Screen reader behavior

A screen reader announces the group as "Avatar group with N members, list" followed by each avatar individually. Each avatar is announced using the username property (e.g., "John Doe's avatar, image"). Avatars without an image source fall back to the initials text. When the overflow button is present, it is announced as "Show N more users, button".

Component Folder Structure

AvatarGroup/
├── AvatarGroup.tsx              # Composition — data processing + layout
├── AvatarItem.tsx               # Individual avatar wrapper with positioning
├── AvtarGroupOverflowButton.tsx # "+N more" overflow indicator
├── hooks.ts                     # Spacing calculation and keyboard navigation
├── types.ts                     # Type definitions
├── index.ts                     # Public API exports
└── README.md

Props

AvatarGroup

PropTypeDefaultDescription
usersUserData[][]Array of user objects to display
size"small" | "medium" | "large" | "xlarge""medium"Size of each avatar
maxDisplaynumber5Maximum visible avatars before overflow
layout"stack" | "grid""stack"Layout mode for the group
spacing"tight" | "normal" | "loose" | number"normal"Overlap offset for stack layout
renderOverflow(count: number) => React.ReactNodeCustom render for the overflow indicator
onAvatarClick(user: UserData, index: number) => voidClick handler for individual avatars
onOverflowClick(hiddenUsers: UserData[]) => voidClick handler for the overflow button

UserData

PropTypeDescription
idstring | number | nullUnique identifier for the user
srcstringImage URL for the avatar
usernamestringDisplay name, used as alt text
altstringAlt text for the avatar image
fallbackstringInitials shown when no image
status"online" | "offline" | "away" | "busy"Status indicator on the avatar

Size Reference

SizeDimensions
small32x32px
medium48x48px
large64x64px
xlarge96x96px

Spacing Reference

SpacingStack Offset
tight-12px
normal-8px
loose-4px

On this page