Avatar Stack Collection
A versatile set of avatar stacks with various interaction models including expansion, glassmorphism, tooltips, and vertical layouts.
Preview
Live interactive preview.
Installation
Add this component to your project using the CLI:
npx -y vui-registry-cli-v1@latest add avatar-stackSource Code
'use client'
import React from 'react'
import { cn } from '@/lib/utils'
import { Avatar, AvatarStackProps } from './avatar'
// Variant 4: Add Button Stack
export function AvatarStackAdd({ users, className }: AvatarStackProps) {
return (
<div className={cn('flex items-center', className)}>
<div className="flex -space-x-2 mr-3">
{users.slice(0, 3).map((user) => (
<Avatar key={user.id} src={user.image} alt={user.name} size={36} className="ring-2 ring-background" />
))}
</div>
<button className="flex items-center justify-center w-9 h-9 rounded-full border border-dashed border-muted-foreground/30 text-muted-foreground hover:border-primary hover:text-primary transition-colors">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M12 5v14M5 12h14" />
</svg>
</button>
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
interface User {
id: string
name: string
image?: string
}
export interface AvatarStackProps {
users: User[]
max?: number
className?: string
size?: number
}
export function AvatarStackCascade({ users, max = 6, className, size = 44 }: AvatarStackProps) {
return (
<div className={cn('flex items-end gap-2', className)}>
{users.slice(0, max).map((user, i) => (
<motion.div
key={user.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
className="relative"
style={{ transform: `translateY(${(i % 3) * 6}px)` }}
>
<Avatar className="ring-2 ring-background" style={{ width: size, height: size }}>
<AvatarImage src={user.image} alt={user.name} className="object-cover" />
<AvatarFallback className="bg-muted text-[10px] font-bold uppercase">
{user.name.slice(0, 2)}
</AvatarFallback>
</Avatar>
</motion.div>
))}
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'
import { Avatar, AvatarStackProps } from './avatar'
// Variant 2: Glassmorphic Spread Stack
export function AvatarStackGlass({ users, max = 5, className, size = 48 }: AvatarStackProps) {
return (
<div className={cn('flex items-center -space-x-4 p-2', className)}>
{users.slice(0, max).map((user, i) => (
<motion.div
key={user.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.05 }}
className="relative z-10 hover:z-20 transition-all duration-300 hover:-translate-y-2"
>
<div className="relative rounded-full p-[2px] bg-gradient-to-b from-white/20 to-white/5 backdrop-blur-md border border-white/10 shadow-lg">
<Avatar src={user.image} alt={user.name} size={size} className="border-none" />
</div>
</motion.div>
))}
</div>
)
}
Source Code
'use client'
import React from 'react'
import { AvatarStack } from './avatar-stack-standard'
import { AvatarStackGlass } from './avatar-stack-glass'
import { AvatarStackTooltip } from './avatar-stack-tooltip'
import { AvatarStackAdd } from './avatar-stack-add'
import { AvatarStackVertical } from './avatar-stack-vertical'
// Main Export for Preview Page
export function AvatarStackPreview() {
const users = [
{ id: '1', name: 'Alice', image: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=64&h=64&q=80' },
{ id: '2', name: 'Bob', image: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=64&h=64&q=80' },
{ id: '3', name: 'Charlie', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=64&h=64&q=80' },
{ id: '4', name: 'Diana', image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=64&h=64&q=80' },
{ id: '5', name: 'Evan', image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=64&h=64&q=80' },
{ id: '6', name: 'Fiona', image: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=64&h=64&q=80' },
]
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 w-full max-w-4xl p-4">
<div className="space-y-6">
<div>
<p className="text-xs text-muted-foreground uppercase mb-2">Standard Expandable</p>
<AvatarStack users={users} />
</div>
<div>
<p className="text-xs text-muted-foreground uppercase mb-2">Glassmorphic Spread</p>
<AvatarStackGlass users={users} />
</div>
<div>
<p className="text-xs text-muted-foreground uppercase mb-2">With Tooltip</p>
<AvatarStackTooltip users={users} />
</div>
</div>
<div className="space-y-6">
<div>
<p className="text-xs text-muted-foreground uppercase mb-2">With Add Button</p>
<AvatarStackAdd users={users} />
</div>
<div>
<p className="text-xs text-muted-foreground uppercase mb-2">Vertical Stack</p>
<AvatarStackVertical users={users} />
</div>
</div>
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
interface User {
id: string
name: string
image?: string
}
export interface AvatarStackProps {
users: User[]
max?: number
className?: string
size?: number
}
export function AvatarStackRing({ users, max = 5, className, size = 48 }: AvatarStackProps) {
return (
<div className={cn('flex items-center -space-x-4', className)}>
{users.slice(0, max).map((user, i) => (
<motion.div
key={user.id}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: i * 0.06 }}
className="relative z-10 hover:z-20"
>
<Avatar
className={cn('ring-2 ring-background shadow-sm hover:ring-primary/50 transition-all')}
style={{ width: size, height: size }}
>
<AvatarImage src={user.image} alt={user.name} className="object-cover" />
<AvatarFallback className="bg-muted text-[10px] font-bold uppercase">
{user.name.slice(0, 2)}
</AvatarFallback>
</Avatar>
</motion.div>
))}
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'
import { Avatar, AvatarStackProps } from './avatar'
// Variant 1: Standard Stack with Expansion
export function AvatarStack({ users, max = 4, className, size = 40 }: AvatarStackProps) {
const displayUsers = users.slice(0, max)
const remaining = users.length - max
return (
<div className={cn('flex items-center -space-x-3 hover:space-x-1 transition-all duration-300', className)}>
{displayUsers.map((user, i) => (
<motion.div
key={user.id}
initial={{ opacity: 0, scale: 0.5, x: -20 }}
animate={{ opacity: 1, scale: 1, x: 0 }}
transition={{ delay: i * 0.1 }}
className="relative group cursor-pointer z-10 hover:z-20 hover:scale-110 transition-transform"
>
<Avatar src={user.image} alt={user.name} size={size} className="ring-2 ring-background shadow-sm" />
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-popover text-popover-foreground text-[10px] px-2 py-1 rounded shadow-lg whitespace-nowrap pointer-events-none z-50">
{user.name}
</div>
</motion.div>
))}
{remaining > 0 && (
<div
className="relative z-0 flex items-center justify-center rounded-full bg-muted text-muted-foreground text-xs font-medium ring-2 ring-background hover:bg-muted/80 transition-colors cursor-pointer"
style={{ width: size, height: size }}
>
+{remaining}
</div>
)}
</div>
)
}
Source Code
'use client'
import React from 'react'
import { cn } from '@/lib/utils'
import { Avatar, AvatarStackProps } from './avatar'
// Variant 3: Tooltip Stack
export function AvatarStackTooltip({ users, max = 3, className }: AvatarStackProps) {
return (
<div className={cn('flex items-center gap-1', className)}>
{users.slice(0, max).map((user) => (
<div key={user.id} className="relative group">
<Avatar src={user.image} alt={user.name} size={32} className="cursor-pointer hover:ring-2 ring-primary transition-all" />
<div className="absolute bottom-full mb-2 left-1/2 -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity bg-black text-white text-xs py-1 px-2 rounded pointer-events-none whitespace-nowrap">
{user.name}
<div className="absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-black" />
</div>
</div>
))}
<div className="text-xs text-muted-foreground ml-2">
and {users.length - max} others
</div>
</div>
)
}
Source Code
'use client'
import React from 'react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'
import { Avatar, AvatarStackProps } from './avatar'
// Variant 5: Vertical Stack
export function AvatarStackVertical({ users, className }: AvatarStackProps) {
return (
<div className={cn('flex flex-col -space-y-2', className)}>
{users.slice(0, 4).map((user, i) => (
<motion.div
key={user.id}
initial={{ x: -20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: i * 0.1 }}
className="relative hover:z-10 hover:translate-x-2 transition-transform"
>
<Avatar src={user.image} alt={user.name} size={40} className="ring-2 ring-background shadow-md" />
</motion.div>
))}
</div>
)
}
Dependencies
framer-motion: latestclsx: latesttailwind-merge: latestlucide-react: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| users | User[] | [] | Array of user objects with id, name, and image. |
| max | number | 4 | Maximum number of avatars to show before truncation. |
| size | number | 40 | Size of each avatar in pixels. |
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.