Avatar Group

A visually interactive avatar group with hover effects and tooltips, allowing users to easily view and highlight avatars with smooth animations.

Preview

JD
SS
AW
EJ

Installation

Install dependencies

# Install Shadcn Avatar component
npx shadcn@latest add avatar

Create the component file

mkdir -p components/shsfui/avatar && touch components/shsfui/avatar/avatar-group.tsx

Add the component code

Open the newly created file and paste the following code:

"use client";
 
import * as React from "react";
import {
  Avatar,
  AvatarImage,
  AvatarFallback,
} from "@/components/ui/avatar";
import { cn } from "@/utils/cn";
 
type AvatarItemType = {
  src: string;
  alt: string;
  initials: string;
};
 
type AvatarGroupProps = {
  avatars?: AvatarItemType[];
  maxDisplay?: number;
  showIndicator?: boolean;
  className?: string;
};
 
export const DEFAULT_AVATARS: AvatarItemType[] = [
  {
    src: "https://randomuser.me/api/portraits/men/32.jpg",
    alt: "John Doe",
    initials: "JD",
  },
  {
    src: "https://randomuser.me/api/portraits/women/44.jpg",
    alt: "Sarah Smith",
    initials: "SS",
  },
  {
    src: "https://randomuser.me/api/portraits/men/91.jpg",
    alt: "Alex Wong",
    initials: "AW",
  },
  {
    src: "https://randomuser.me/api/portraits/women/17.jpg",
    alt: "Emma Johnson",
    initials: "EJ",
  },
];
 
const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
  (props, ref) => {
    const {
      avatars = DEFAULT_AVATARS,
      maxDisplay = avatars.length,
      showIndicator = false,
      className,
    } = props;
 
    const displayAvatars = React.useMemo(
      () => avatars.slice(0, maxDisplay),
      [avatars, maxDisplay]
    );
 
    const remainingCount = avatars.length - maxDisplay;
 
    return (
      <div
        ref={ref}
        className={cn(
          "bg-background flex items-center justify-center rounded-full border p-1",
          className
        )}
      >
        <div className="flex items-center relative">
          {displayAvatars.map((avatar, index) => (
            <div
              key={index}
              className={cn("relative hover:z-10", index > 0 && "-ml-2")}
            >
              <Avatar className="transition-all duration-300 hover:scale-105 hover:-translate-y-1 hover:shadow-lg border-2 border-background">
                <AvatarImage src={avatar.src} alt={avatar.alt} />
                <AvatarFallback>{avatar.initials}</AvatarFallback>
              </Avatar>
              {showIndicator && (
                <span className="absolute bottom-0 right-0 block h-2 w-2 rounded-full bg-green-500 ring-2 ring-background" />
              )}
            </div>
          ))}
 
          {remainingCount > 0 && (
            <div className={cn("relative hover:z-10", "-ml-2")}>
              <Avatar className="transition-all duration-300 hover:scale-105 hover:-translate-y-1 hover:shadow-lg border-2 border-background bg-muted">
                <AvatarFallback>+{remainingCount}</AvatarFallback>
              </Avatar>
            </div>
          )}
        </div>
      </div>
    );
  }
);
 
AvatarGroup.displayName = "AvatarGroup";
 
export AvatarGroup;