Animated Avatar Group
smoothui-registry/animated-avatar-groupFreeComponent
Stack of overlapping avatars with smooth expand/collapse animation
Install it
npx shadcn@latest add https://www.smoothui.dev/r/animated-avatar-group.jsonSource
1"use client";23import { cn } from "@/lib/utils";4import { motion, useReducedMotion } from "motion/react";5import { useEffect, useState } from "react";67export type AvatarData = {8 src: string;9 alt: string;10 href?: string;11};1213export type AnimatedAvatarGroupProps = {14 avatars: AvatarData[];15 maxVisible?: number;16 size?: number;17 overlap?: number;18 className?: string;19 expandOnHover?: boolean;20};2122const AnimatedAvatarGroup = ({23 avatars,24 maxVisible = 4,25 size = 40,26 overlap = 0.3,27 className,28 expandOnHover = true,29}: AnimatedAvatarGroupProps) => {30 const shouldReduceMotion = useReducedMotion();31 const [isHovered, setIsHovered] = useState(false);32 const [isHoverDevice, setIsHoverDevice] = useState(false);3334 useEffect(() => {35 const mediaQuery = window.matchMedia("(hover: hover) and (pointer: fine)");36 setIsHoverDevice(mediaQuery.matches);3738 const handleChange = (event: MediaQueryListEvent) => {39 setIsHoverDevice(event.matches);40 };4142 mediaQuery.addEventListener("change", handleChange);43 return () => {44 mediaQuery.removeEventListener("change", handleChange);45 };46 }, []);4748 const visibleAvatars = avatars.slice(0, maxVisible);49 const hiddenCount = avatars.length - maxVisible;50 const hasHiddenAvatars = hiddenCount > 0;5152 const overlapPx = size * overlap;53 const expanded = expandOnHover && isHoverDevice && isHovered;5455 const springTransition = shouldReduceMotion56 ? { duration: 0 }57 : { bounce: 0.1, duration: 0.25, type: "spring" as const };5859 return (60 <motion.div61 aria-label="Avatar group"62 className={cn("flex items-center", className)}63 onMouseEnter={() => setIsHovered(true)}64 onMouseLeave={() => setIsHovered(false)}65 role="group"66 >67 {visibleAvatars.map((avatar, index) => {68 const marginLeft = index === 0 ? 0 : expanded ? 4 : -overlapPx;6970 const content = (71 <motion.img72 alt={avatar.alt}73 animate={74 shouldReduceMotion75 ? { opacity: 1 }76 : {77 opacity: 1,78 scale: expanded ? 1.05 : 1,79 }80 }81 className="rounded-full object-cover"82 draggable={false}83 height={size}84 initial={85 shouldReduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1 }86 }87 src={avatar.src}88 style={{89 height: size,90 width: size,91// … truncated
What it pulls in
npm packages
Files it writes
More from SmoothUI Registry
All 178 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Agent Avataragent-avatar | SmoothUI Registry | Components | Free | no deps | |
| Ai Approvalai-approval | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Artifactai-artifact | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Branchai-branch | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Citationai-citation | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Context Meterai-context-meter | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Conversationai-conversation | SmoothUI Registry | Components | Free | 2 deps | |
| Ai Coreai-core | SmoothUI Registry | Components | Free | 1 dep |
