Skip to content
Dethink Components

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 files

Import the component into your page or component file.

Usage
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.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product

Security audit

Overflow

Six 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.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. June Okafor - Security
  7. 3 more reviewers
Show sourceexamples/avatar-group/basic.tsx
examples/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.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. June Okafor - Security
  7. 2 more
overlap: none
Noah Reyes - Design systems; Eli Morgan - Frontend; Mira Patel - Operations; Leo Novak - Product; Sam Rivera - QA; June Okafor - Security. 2 more.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. June Okafor - Security
  7. 2 more
overlap: sm
Noah Reyes - Design systems; Eli Morgan - Frontend; Mira Patel - Operations; Leo Novak - Product; Sam Rivera - QA; June Okafor - Security. 2 more.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. June Okafor - Security
  7. 2 more
overlap: md
Noah Reyes - Design systems; Eli Morgan - Frontend; Mira Patel - Operations; Leo Novak - Product; Sam Rivera - QA; June Okafor - Security. 2 more.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. June Okafor - Security
  7. 2 more
overlap: lg
Show sourceexamples/avatar-group/overlap.tsx
examples/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.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. June Okafor - Security
  7. 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.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. 1 more reviewer
  • Noah ReyesDesign systemsReviewer
  • Eli MorganFrontendReviewer
  • Mira PatelOperationsReviewer
  • Leo NovakProductReviewer
  • Sam RiveraQAReviewer
Show sourceexamples/avatar-group/reveal.tsx
examples/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.

Review queue

AvatarGroup summarizes owners while Badge communicates workflow state.

Registry smoke

3 assigned reviewers

Noah Reyes - Design systems; Eli Morgan - Frontend; Mira Patel - Operations.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
Ready

Token migration

5 assigned reviewers

Eli Morgan - Frontend; Mira Patel - Operations; Leo Novak - Product; Sam Rivera - QA; June Okafor - Security. 2 more reviewers.
  1. Eli Morgan - Frontend
  2. Mira Patel - Operations
  3. Leo Novak - Product
  4. Sam Rivera - QA
  5. June Okafor - Security
  6. 2 more reviewers
Reviewing

Access policy

6 assigned reviewers

Noah Reyes - Design systems; Eli Morgan - Frontend; Mira Patel - Operations; Leo Novak - Product; Sam Rivera - QA; June Okafor - Security. 3 more reviewers.
  1. Noah Reyes - Design systems
  2. Eli Morgan - Frontend
  3. Mira Patel - Operations
  4. Leo Novak - Product
  5. Sam Rivera - QA
  6. June Okafor - Security
  7. 3 more reviewers
Blocked
Show sourceexamples/avatar-group/recipe-review-queue.tsx
examples/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.

AvatarGroup props
PropWhat it doesDefault
membersreadonly AvatarGroupMember[]People, systems, or teams to render. Each member accepts the Avatar identity props plus metadata.Not set
labelstringAccessible group label and basis for overflow copy."Avatar group"
maxnumberMaximum 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) => stringCustom 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"