Avatar group
Overlapping avatars that spread apart on hover.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/avatar-group.jsonEmma CollinsMarcus JohnsonAva MitchellJasmine BrooksDaniel Kim+2
Emma CollinsMarcus JohnsonAva Mitchell
Hover to spread them out
components/examples/avatar-group-demo.tsx
"use client";
import { AvatarGroup } from "@/components/ui/avatar-group";
const team = [
{ name: "Emma Collins", hue: 20 },
{ name: "Marcus Johnson", hue: 200 },
{ name: "Ava Mitchell", hue: 140 },
{ name: "Jasmine Brooks", hue: 330 },
{ name: "Daniel Kim", hue: 260 },
{ name: "Priya Natarajan", hue: 60 },
{ name: "Leo Fischer", hue: 100 },
];
export default function AvatarGroupDemo() {
return (
<div className="flex flex-col items-center gap-6">
<AvatarGroup people={team} max={5} size={40} />
<AvatarGroup people={team.slice(0, 3)} size={28} />
<p className="text-sm text-muted-foreground">Hover to spread them out</p>
</div>
);
}When to use
- Showing who is on a team, document or thread.
When not to use
- A full member list: use a table.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/avatar-group.json1Install the dependencies
npm install motion2Copy the source into your project
components/ui/avatar-group.tsx"use client"; import * as React from "react"; import { motion } from "motion/react"; import { cn } from "@/lib/utils"; export interface Person { name: string; src?: string; /** Hue (0–360) for the initials background when there's no image. */ hue?: number; } export interface AvatarProps extends Person { size?: number; className?: string; } export function Avatar({ name, src, hue, size = 32, className }: AvatarProps) { const initials = name.split(/\s+/).map((p) => p[0]).slice(0, 2).join("").toUpperCase(); const h = hue ?? [...name].reduce((n, c) => n + c.charCodeAt(0), 0) % 360; return ( <span className={cn("relative inline-grid shrink-0 place-items-center overflow-hidden rounded-full font-semibold text-white ring-2 ring-background", className)} style={{ width: size, height: size, fontSize: size * 0.36, background: `oklch(0.62 0.13 ${h})` }} title={name} > {src ? ( // eslint-disable-next-line @next/next/no-img-element <img src={src} alt={name} className="size-full object-cover" /> ) : ( <span aria-hidden>{initials}</span> )} {!src && <span className="sr-only">{name}</span>} </span> ); } export interface AvatarGroupProps { people: Person[]; max?: number; size?: number; className?: string; } /** Overlapping avatars that spread apart on hover. */ export function AvatarGroup({ people, max = 4, size = 32, className }: AvatarGroupProps) { const [hover, setHover] = React.useState(false); const shown = people.slice(0, max); const extra = people.length - shown.length; const overlap = size * 0.32; return ( <div className={cn("flex items-center", className)} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} aria-label={`${people.length} people`} role="group" > {shown.map((p, i) => ( <motion.span key={p.name} animate={{ marginLeft: i === 0 ? 0 : hover ? 4 : -overlap }} transition={{ type: "spring", stiffness: 400, damping: 28 }} style={{ zIndex: shown.length - i }} > <Avatar {...p} size={size} /> </motion.span> ))} {extra > 0 && ( <motion.span animate={{ marginLeft: hover ? 4 : -overlap }} className="relative grid place-items-center rounded-full bg-muted font-medium text-muted-foreground ring-2 ring-background" style={{ width: size, height: size, fontSize: size * 0.34 }} > +{extra} </motion.span> )} </div> ); }
Usage
Import it from @/components/ui/avatar-group after installing.
example.tsx
import { AvatarGroup } from "@/components/ui/avatar-group";
export function Viewers() {
return <AvatarGroup people={[{ name: "Ada Lovelace" }, { name: "Grace Hopper" }, { name: "Alan Turing" }]} max={4} />;
}API reference
AvatarGroup
Overlapping avatars with a +N chip.
| Prop | Type | Default | Description |
|---|---|---|---|
| people | { name; src?; hue? }[] | – | People to show. |
| max | number | 4 | Shown before +N. |
| size | number | 32 | Diameter in px. |
Avatar
One avatar: image or initials on a hue generated from the name.
| Prop | Type | Default | Description |
|---|---|---|---|
| name / src / hue / size | string / string / number / number | – | Identity and size. |
Accessibility
- Initials avatars include the full name for screen readers; images use the name as alt.
Motion
- Overlap springs open on hover.
Responsive behavior
- Fixed size; reduce max on phones.
Performance
- Images load lazily by browser default.
Notes for AI
- Pass hue to keep a person's color stable.
This page as Markdown, for assistants and coding agents:
/components/avatar-group/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt