Skip to content

AvatarGroup

MD, under 1950B gzipped

An overlapping avatar stack with an accessible overflow indicator.

Category
Data display
Budget
md, several states or a live subscription
Runtime deps
None

Preview

Preview language

This is the exact fixture the conformance suite renders in CI. Switch the language to see the component mirror and reformat.

Install

npx shadcn@latest add https://kata-ui-rho.vercel.app/r/avatar-group.json

Copies the source into your project. Pulls in 2 primitives: cn, avatar.

Source

import { cn } from "../lib/cn";
import { Avatar } from "./avatar";

export interface AvatarGroupProps {
  people: Array<{ name: string; src?: string }>;
  max?: number;
  size?: number;
  className?: string;
}

/**
 * An overlapping stack of avatars with a "+N" overflow indicator.
 *
 * The overflow indicator gets a real accessible name listing who it hides —
 * "+3 more: Aisha, Bo, Chidi" — rather than leaving a screen reader user with
 * an unexplained number.
 */
export function AvatarGroup({ people, max = 4, size = 32, className }: AvatarGroupProps) {
  const visible = people.slice(0, max);
  const overflow = people.slice(max);

  return (
    <ul
      className={cn("flex -space-x-2 rtl:space-x-reverse", className)}
      aria-label={`${people.length} people`}
    >
      {visible.map((person, index) => (
        <li key={`${person.name}-${index}`} className="ring-2 ring-white rounded-full dark:ring-neutral-950">
          <Avatar name={person.name} src={person.src} size={size} />
        </li>
      ))}

      {overflow.length > 0 && (
        <li className="ring-2 ring-white rounded-full dark:ring-neutral-950">
          {/* role="img" belongs on the span, not the <li> — axe flags role="img"
              as disallowed directly on a list item, and it also strips the
              item's implicit listitem role from the accessibility tree. */}
          <span
            role="img"
            aria-label={`+${overflow.length} more: ${overflow.map((p) => p.name).join(", ")}`}
            className="flex items-center justify-center rounded-full bg-neutral-300 font-medium text-neutral-700 dark:bg-neutral-600 dark:text-neutral-100"
            style={{ width: size, height: size, fontSize: size * 0.35 }}
          >
            <span aria-hidden="true">+{overflow.length}</span>
          </span>
        </li>
      )}
    </ul>
  );
}

What CI checks

← All components