Avatar Stack
shadcncraft/avatar-stackFreeComponent
A compact, visually appealing component for displaying multiple user avatars in an overlapping stack. Perfect for showing participants in a group chat, team members, or active collaborators. Supports customizable size, max visible avatars, border styling, and optional '+n' indicator for overflow. Renders gracefully from 1 to dozens of users while maintaining a small footprint.
Live preview
live · rendered by the registry
Rendered by shadcncraft on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://shadcncraft.com/r/avatar-stack.jsonSource
1import * as React from "react";23import { cn } from "@/lib/utils";45interface AvatarStackProps extends React.ComponentProps<"div"> {6 orientation?: "horizontal" | "vertical";7 max?: number;8 avatarSize?: number;9 overlapRatio?: number;10 mask?: boolean;11}1213export function AvatarStack({14 children,15 className,16 orientation = "horizontal",17 max, // Max visible avatars before "+N"18 avatarSize, // Avatar size in pixels19 overlapRatio = 0.2, // 20% of avatar size20 mask = true,21 style,22 ...props23}: AvatarStackProps) {24 const items = React.Children.toArray(children);25 const overflow = max ? Math.max(0, items.length - max) : 0;26 const visible = items.slice(0, max);2728 // Use the first child as the chip wrapper so its size, shape, and styling29 // (Avatar from any registry, plain div, etc.) automatically apply to "+N".30 const firstChild = visible.find(React.isValidElement);3132 // `avatarSize` set: force children + chip to that pixel size; derive overlap from it.33 // `avatarSize` omitted: children keep intrinsic size; overlap falls back to 0.5rem.34 // Override the fallback overlap via `style={{ "--overlap": "..." }}` if needed.35 const overlap = avatarSize != null ? `${overlapRatio * avatarSize}px` : "0.5rem";3637 return (38 <div39 data-slot="avatar-stack"40 data-orientation={orientation}41 className={cn(42 "group isolate flex items-center",43 "data-[orientation=horizontal]:flex-row data-[orientation=horizontal]:-space-x-(--overlap)",44 "data-[orientation=vertical]:flex-col data-[orientation=vertical]:-space-y-(--overlap)",45 "*:shrink-0 *:object-cover *:object-center",46 avatarSize != null && "*:size-(--avatar-size)!",47 mask && "*:ring-2 *:ring-background",48 className49 )}50 style={51 {52 ...style,53 "--overlap": overlap,54 ...(avatarSize != null && { "--avatar-size": `${avatarSize}px` }),55 } as React.CSSProperties56 }57 {...props}58 >59 {visible}60 {overflow > 0 &&61 firstChild &&62 React.cloneElement(63 firstChild as React.ReactElement<Record<string, unknown>>,64 { key: "overflow", "data-slot": "avatar-stack-overflow" },65 <span className="flex size-full items-center justify-center rounded-[inherit] bg-muted text-xs font-medium text-muted-foreground">66 +{overflow}67 </span>68 )}69 </div>70 );71}
Files it writes
More from shadcncraft
All 26 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Badgebadge | shadcncraft | Components | Free | no deps | |
| Featured Iconfeatured-icon | shadcncraft | Components | Free | no deps | |
| Image Zoomimage-zoom | shadcncraft | Components | Free | 1 dep | |
| Marqueemarquee | shadcncraft | Components | Free | no deps | |
| Page Headingpage-heading | shadcncraft | Components | Free | no deps | |
| Placeholder Logoplaceholder-logo | shadcncraft | Components | Free | no deps | |
| Profile Cardprofile-card | shadcncraft | Components | Free | no deps | |
| Section Headingsection-heading | shadcncraft | Components | Free | no deps |
