Avatar Group

Overlapping avatar stack for participants, assignees, and collaborative contexts.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/avatar-group.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

2 stories available:

Code

import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../../lib/utils"; import { Avatar, AvatarFallback, AvatarImage } from "../avatar/avatar"; const avatarGroupVariants = cva("flex items-center", { defaultVariants: { size: "md", }, variants: { size: { lg: "-space-x-4", md: "-space-x-3", sm: "-space-x-2.5", }, }, }); const avatarItemVariants = cva( "ring-2 ring-background border border-border bg-muted text-muted-foreground", { defaultVariants: { size: "md", }, variants: { size: { lg: "size-12 text-sm", md: "size-10 text-xs", sm: "size-8 text-[11px]", }, }, }, ); const overflowBadgeVariants = cva( "inline-flex shrink-0 items-center justify-center rounded-full border border-border bg-muted font-medium text-muted-foreground ring-2 ring-background", { defaultVariants: { size: "md", }, variants: { size: { lg: "h-12 min-w-12 px-3 text-sm", md: "h-10 min-w-10 px-2.5 text-xs", sm: "h-8 min-w-8 px-2 text-[11px]", }, }, }, ); export type AvatarGroupItem = { alt: string; fallback: string; src?: string; }; export type AvatarGroupProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof avatarGroupVariants> & { items: AvatarGroupItem[]; max?: number; overflowLabel?: (hiddenCount: number) => string; }; const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>( ({ className, items, max, overflowLabel, size, ...props }, reference) => { const visibleItems = max === undefined ? items : items.slice(0, max); const hiddenCount = max === undefined ? 0 : Math.max(items.length - max, 0); return ( <div className={cn(avatarGroupVariants({ size }), className)} ref={reference} {...props} > {visibleItems.map((item, index) => ( <Avatar className={avatarItemVariants({ size })} key={item.src ?? item.alt} style={{ zIndex: visibleItems.length - index }} > {item.src ? <AvatarImage alt={item.alt} src={item.src} /> : null} <AvatarFallback>{item.fallback}</AvatarFallback> </Avatar> ))} {hiddenCount > 0 ? ( <span className={overflowBadgeVariants({ size })}> {overflowLabel ? overflowLabel(hiddenCount) : `+${hiddenCount}`} </span> ) : null} </div> ); }, ); AvatarGroup.displayName = "AvatarGroup"; export { AvatarGroup, avatarGroupVariants, avatarItemVariants };

Dependencies

  • @vllnt/ui@^0.2.1