ComponentsAvatarGroup
AvatarGroup
Show a group of people with overlapping avatars.
On this page
Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.
View registry filesImport the component into your page or component file.
import { AvatarGroup, type AvatarGroupMember } from "@dethink/components";
const members: AvatarGroupMember[] = [
{ name: "Noah Reyes", src: "/avatars/noah.png" },
{ name: "Mira Patel", src: "/avatars/mira.png" },
];
export function Example() {
return <AvatarGroup label="Reviewers" members={members} max={3} />;
}Try the examples, then open the code to use them in your app.
Stacked groups
Keep the visible cluster compact while the hidden member count remains accessible and readable.
Launch readiness
Four reviewers assigned
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
Security audit
OverflowSix members with three visible avatars
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
- Sam Rivera - QA
- June Okafor - Security
- 3 more reviewers
Show sourceexamples/avatar-group/basic.tsx
"use client";
import { AvatarGroup, Badge } from "@dethink/components";
import { avatarMembers } from "@/examples/_shared/avatar-members";
export function AvatarGroupBasic() {
return (
<div className="grid w-full max-w-lg gap-5">
<div className="border-border flex min-w-0 flex-wrap items-center justify-between gap-[var(--dt-space-4)] rounded-md border p-4">
<div className="min-w-0">
<p className="truncate text-sm font-medium">Launch readiness</p>
<p className="text-muted-foreground truncate text-xs">
Four reviewers assigned
</p>
</div>
<AvatarGroup
label="Launch readiness reviewers"
max={4}
members={avatarMembers.slice(0, 4)}
ring="border"
size="md"
/>
</div>
<div className="border-border flex min-w-0 flex-wrap items-center justify-between gap-[var(--dt-space-4)] rounded-md border p-4">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<p className="truncate text-sm font-medium">Security audit</p>
<Badge size="xs" tone="warning">
Overflow
</Badge>
</div>
<p className="text-muted-foreground truncate text-xs">
Six members with three visible avatars
</p>
</div>
<AvatarGroup
label="Security audit reviewers"
max={3}
members={avatarMembers}
overflowLabel={({ count }) => `${count} more reviewers`}
ring="ring"
size="md"
/>
</div>
</div>
);
}Overlap
Tune overlap for dense tables, roomy cards, or standalone ownership summaries.
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
- Sam Rivera - QA
- June Okafor - Security
- 2 more
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
- Sam Rivera - QA
- June Okafor - Security
- 2 more
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
- Sam Rivera - QA
- June Okafor - Security
- 2 more
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
- Sam Rivera - QA
- June Okafor - Security
- 2 more
Show sourceexamples/avatar-group/overlap.tsx
"use client";
import { AvatarGroup, type AvatarGroupOverlap } from "@dethink/components";
import { avatarMembers } from "@/examples/_shared/avatar-members";
const overlaps: AvatarGroupOverlap[] = ["none", "sm", "md", "lg"];
export function AvatarGroupOverlapExample() {
return (
<div className="grid w-full gap-5 sm:grid-cols-2">
{overlaps.map((overlap) => (
<div
key={overlap}
className="border-border grid justify-items-start gap-3 rounded-md border p-4"
>
<AvatarGroup
label={`${overlap} overlap reviewers`}
max={4}
members={avatarMembers}
overlap={overlap}
overflowLabel={({ count }) => `${count} more`}
size="lg"
/>
<span className="text-muted-foreground text-xs">
overlap: {overlap}
</span>
</div>
))}
</div>
);
}Reveal behavior
Spread reveal is pointer-friendly, while names reveal keeps keyboard focus and reduced-motion behavior readable.
Spread reveal
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
- Sam Rivera - QA
- June Okafor - Security
- 2 more reviewers
Persistent roster
Names stay in the row instead of floating below the avatar stack.
- Noah Reyes - Design systems
- Eli Morgan - Frontend
- Mira Patel - Operations
- Leo Novak - Product
- Sam Rivera - QA
- 1 more reviewer
- Noah ReyesDesign systemsReviewer
- Eli MorganFrontendReviewer
- Mira PatelOperationsReviewer
- Leo NovakProductReviewer
- Sam RiveraQAReviewer
Show sourceexamples/avatar-group/reveal.tsx
"use client";
import { AvatarGroup, Badge } from "@dethink/components";
import { avatarMembers } from "@/examples/_shared/avatar-members";
const rosterMembers = avatarMembers.slice(0, 5);
export function AvatarGroupReveal() {
return (
<div className="grid w-full gap-6">
<div className="border-border grid gap-3 rounded-md border p-5">
<p className="text-sm font-medium">Spread reveal</p>
<AvatarGroup
label="Spread reveal reviewers"
max={4}
members={avatarMembers}
overflowLabel={({ count }) => `${count} more reviewers`}
reveal="spread"
ring="ring"
size="lg"
/>
</div>
<div className="border-border grid gap-3 rounded-md border p-5">
<div className="flex min-w-0 flex-wrap items-center justify-between gap-[var(--dt-space-4)]">
<div className="min-w-0">
<p className="text-sm font-medium">Persistent roster</p>
<p className="text-muted-foreground text-xs">
Names stay in the row instead of floating below the avatar stack.
</p>
</div>
<AvatarGroup
label="Persistent roster reviewers"
max={4}
members={rosterMembers}
overflowLabel={({ count }) => `${count} more reviewer`}
ring="border"
size="md"
/>
</div>
<ul className="grid gap-2 sm:grid-cols-2">
{rosterMembers.map((member) => (
<li
key={member.id}
className="border-border/70 flex min-w-0 items-center justify-between gap-[var(--dt-space-3)] rounded-md border px-3 py-2"
>
<span className="min-w-0">
<span className="block truncate text-sm font-medium">
{member.name}
</span>
<span className="text-muted-foreground block truncate text-xs">
{member.metadata}
</span>
</span>
<Badge size="xs" tone={member.tone ?? "neutral"} variant="subtle">
Reviewer
</Badge>
</li>
))}
</ul>
</div>
</div>
);
}Review queue recipe
Combine AvatarGroup and Badge to summarize ownership and workflow status in repeated rows.
Show sourceexamples/avatar-group/recipe-review-queue.tsx
"use client";
import { CheckCircle2, Clock3, ShieldAlert } from "lucide-react";
import {
AvatarGroup,
Badge,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@dethink/components";
import { avatarMembers } from "@/examples/_shared/avatar-members";
const queues = [
{
icon: CheckCircle2,
members: avatarMembers.slice(0, 3),
name: "Registry smoke",
status: "Ready",
tone: "success" as const,
},
{
icon: Clock3,
members: avatarMembers.slice(1, 6),
name: "Token migration",
status: "Reviewing",
tone: "warning" as const,
},
{
icon: ShieldAlert,
members: avatarMembers,
name: "Access policy",
status: "Blocked",
tone: "destructive" as const,
},
];
export function AvatarGroupReviewQueue() {
return (
<Card className="w-full max-w-2xl">
<CardHeader>
<CardTitle>Review queue</CardTitle>
<CardDescription>
AvatarGroup summarizes owners while Badge communicates workflow state.
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4">
{queues.map(({ icon: Icon, members, name, status, tone }) => (
<div
key={name}
className="flex min-w-0 flex-wrap items-center justify-between gap-[var(--dt-space-4)]"
>
<div className="min-w-0">
<p className="truncate text-sm font-medium">{name}</p>
<p className="text-muted-foreground truncate text-xs">
{members.length} assigned reviewers
</p>
</div>
<div className="flex shrink-0 items-center gap-[var(--dt-space-3)]">
<AvatarGroup
label={`${name} reviewers`}
max={3}
members={members}
overflowLabel={({ count }) => `${count} more reviewers`}
size="sm"
/>
<Badge icon={<Icon />} size="xs" tone={tone}>
{status}
</Badge>
</div>
</div>
))}
</CardContent>
</Card>
);
}AvatarGroupProps extends div attributes while owning group labelling and member rendering.
| Prop | What it does | Default |
|---|---|---|
membersreadonly AvatarGroupMember[] | People, systems, or teams to render. Each member accepts the Avatar identity props plus metadata. | Not set |
labelstring | Accessible group label and basis for overflow copy. | "Avatar group" |
maxnumber | Maximum visible members before an overflow avatar summarizes the hidden members. | 5 |
overlap"none" | "sm" | "md" | "lg" | Amount each avatar overlaps the previous one. | "md" |
overflowLabelstring | (context) => string | Custom accessible and visible label for the overflow avatar. | Not set |
reveal"none" | "spread" | "names" | Optional member-name reveal behavior for pointer and keyboard focus. | "none" |
revealLabelVisibility"hover" | "always" | Controls whether reveal labels are interaction-driven or persistent. | "hover" |
size"xs" | "sm" | "md" | "lg" | "xl" | "2xl" | Shared Avatar size for members unless a member overrides it. | "md" |
shape"circle" | "rounded" | "square" | Shared Avatar shape for members unless a member overrides it. | "circle" |
ring"none" | "border" | "ring" | Shared Avatar ring for members unless a member overrides it. | "border" |
motion"none" | "subtle" | "standard" | Reveal and zoom motion, reduced automatically for users who prefer less motion. | "standard" |