AniUI

Avatar Group

Overlapping stack of avatars with automatic “+N” overflow — perfect for showing team members or participants.

JD
MK
Web preview — components render natively on iOS & Android
import { Avatar } from "@/components/ui/avatar";
import { AvatarGroup } from "@/components/ui/avatar-group";

export function TeamStack() {
  return (
    <AvatarGroup>
      <Avatar src="https://github.com/anishlp7.png" fallback="AN" />
      <Avatar fallback="JD" />
      <Avatar fallback="MK" />
    </AvatarGroup>
  );
}

Installation#

npx @aniui/cli add avatar-group

Usage#

app/index.tsx
import { Avatar } from "@/components/ui/avatar";
import { AvatarGroup } from "@/components/ui/avatar-group";

export function TeamStack() {
  return (
    <AvatarGroup>
      <Avatar src="https://github.com/anishlp7.png" fallback="AN" />
      <Avatar fallback="JD" />
      <Avatar fallback="MK" />
    </AvatarGroup>
  );
}

Overflow#

Avatars beyond max (default 4) collapse into a “+N” counter.

AN
JD
MK
+3
Web preview — components render natively on iOS & Android
// Only the first `max` avatars render; the rest collapse into "+N"
<AvatarGroup max={3}>
  <Avatar fallback="AN" />
  <Avatar fallback="JD" />
  <Avatar fallback="MK" />
  <Avatar fallback="SR" />
  <Avatar fallback="TW" />
  <Avatar fallback="PB" />
</AvatarGroup>
// Renders 3 avatars followed by a "+3" indicator

Spacing#

Control how tightly the avatars overlap.

AN
JD
MK
AN
JD
MK
AN
JD
MK
Web preview — components render natively on iOS & Android
<AvatarGroup spacing="sm">{/* tight overlap */}</AvatarGroup>
<AvatarGroup spacing="md">{/* default overlap */}</AvatarGroup>
<AvatarGroup spacing="lg">{/* deep overlap */}</AvatarGroup>

Props#

PropTypeDefault
max
number
4
spacing
"sm" | "md" | "lg"
"md"
children
ReactNode
className
string

Also accepts all View props from React Native. Children are typically Avatar components.

Accessibility#

  • Each child Avatar keeps its own accessibility semantics.
  • The “+N” overflow counter is rendered as text and read by screen readers.

Source#

components/ui/avatar-group.tsx
import React from "react";
import { View, Text } from "react-native";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

// spacing is applied as a negative left margin per item (not -space-x-*) because
// neither NativeWind nor Uniwind supports Tailwind's sibling-selector space utilities.
const avatarGroupItemVariants = cva("rounded-full border-2 border-background", {
  variants: {
    spacing: {
      sm: "-ml-2",
      md: "-ml-3",
      lg: "-ml-4",
    },
  },
  defaultVariants: {
    spacing: "md",
  },
});

export interface AvatarGroupProps
  extends React.ComponentPropsWithoutRef<typeof View>,
    VariantProps<typeof avatarGroupItemVariants> {
  className?: string;
  max?: number;
  children?: React.ReactNode;
}

export function AvatarGroup({ spacing, max = 4, className, children, ...props }: AvatarGroupProps) {
  const items = React.Children.toArray(children);
  const visible = items.slice(0, max);
  const overflow = items.length - visible.length;

  return (
    <View className={cn("flex-row items-center", className)} {...props}>
      {visible.map((child, index) => (
        <View
          key={index}
          className={cn(avatarGroupItemVariants({ spacing }), index === 0 && "ml-0")}
        >
          {child}
        </View>
      ))}
      {overflow > 0 && (
        <View
          className={cn(
            avatarGroupItemVariants({ spacing }),
            "h-10 w-10 items-center justify-center bg-muted",
            visible.length === 0 && "ml-0"
          )}
        >
          <Text className="text-sm font-medium text-muted-foreground">+{overflow}</Text>
        </View>
      )}
    </View>
  );
}