AvatarGroup

Display a stacked or grid group of avatars with overflow counting

Usage

import { Avatar, AvatarGroup } from '@hareui/vue'
JDKWECMB
<script setup lang="ts">import { Avatar, AvatarGroup } from '@hareui/vue'const users = [  { id: 1, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg', name: 'John Doe' },  { id: 2, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg', name: 'Kate Wilson' },  { id: 3, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg', name: 'Emily Chen' },  { id: 4, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg', name: 'Michael Brown' },  { id: 5, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg', name: 'Olivia Davis' },]const initials = (name: string) => name.split(' ').map(n => n[0]).join('')</script><template>  <AvatarGroup>    <Avatar v-for="user in users.slice(0, 4)" :key="user.id" :alt="user.name" :fallback="initials(user.name)" :src="user.image" />  </AvatarGroup></template>

Anatomy

Avatar components go in the default slot. HeroUI's AvatarGroup.Count part becomes the #count slot.

<AvatarGroup>
  <Avatar src="…" fallback="JD" />
  <template #count>+3</template> <!-- optional: explicit count (AvatarGroup.Count) -->
</AvatarGroup>

ui keys: base, count.

Examples

Max

JDKWEC+2
<script setup lang="ts">import { Avatar, AvatarGroup } from '@hareui/vue'const users = [  { id: 1, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg', name: 'John Doe' },  { id: 2, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg', name: 'Kate Wilson' },  { id: 3, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg', name: 'Emily Chen' },  { id: 4, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg', name: 'Michael Brown' },  { id: 5, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg', name: 'Olivia Davis' },]const initials = (name: string) => name.split(' ').map(n => n[0]).join('')</script><template>  <AvatarGroup :max="3">    <Avatar v-for="user in users" :key="user.id" :alt="user.name" :fallback="initials(user.name)" :src="user.image" />  </AvatarGroup></template>

With Count

JDKWEC+9
<script setup lang="ts">import { Avatar, AvatarGroup } from '@hareui/vue'const users = [  { id: 1, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg', name: 'John Doe' },  { id: 2, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg', name: 'Kate Wilson' },  { id: 3, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg', name: 'Emily Chen' },  { id: 4, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg', name: 'Michael Brown' },  { id: 5, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg', name: 'Olivia Davis' },]const total = 12const initials = (name: string) => name.split(' ').map(n => n[0]).join('')</script><template>  <AvatarGroup size="sm">    <Avatar v-for="user in users.slice(0, 3)" :key="user.id" :alt="user.name" :fallback="initials(user.name)" :src="user.image" />    <template #count>+{{ total - 3 }}</template>  </AvatarGroup></template>

Sizes

Small

JDKWECMB

Medium (default)

JDKWECMB

Large

JDKWECMB
<script setup lang="ts">import { Avatar, AvatarGroup } from '@hareui/vue'const users = [  { id: 1, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg', name: 'John Doe' },  { id: 2, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg', name: 'Kate Wilson' },  { id: 3, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg', name: 'Emily Chen' },  { id: 4, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg', name: 'Michael Brown' },  { id: 5, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg', name: 'Olivia Davis' },]const initials = (name: string) => name.split(' ').map(n => n[0]).join('')const sizes = [  { size: 'sm', label: 'Small' },  { size: 'md', label: 'Medium (default)' },  { size: 'lg', label: 'Large' },] as const</script><template>  <div class="flex flex-col items-center gap-6">    <div v-for="s in sizes" :key="s.size" class="flex flex-col items-center gap-2">      <p class="text-sm text-muted">{{ s.label }}</p>      <AvatarGroup :size="s.size">        <Avatar v-for="user in users.slice(0, 4)" :key="user.id" :alt="user.name" :fallback="initials(user.name)" :src="user.image" />      </AvatarGroup>    </div>  </div></template>

Grid

JDKWECMBOD
<script setup lang="ts">import { Avatar, AvatarGroup } from '@hareui/vue'const users = [  { id: 1, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg', name: 'John Doe' },  { id: 2, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg', name: 'Kate Wilson' },  { id: 3, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg', name: 'Emily Chen' },  { id: 4, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg', name: 'Michael Brown' },  { id: 5, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg', name: 'Olivia Davis' },]const initials = (name: string) => name.split(' ').map(n => n[0]).join('')</script><template>  <AvatarGroup grid :max="5">    <Avatar v-for="user in users" :key="user.id" :alt="user.name" :fallback="initials(user.name)" :src="user.image" />  </AvatarGroup></template>

Overlap

clip

JDABECSM+2

ring

JDABECSM+2
<script setup lang="ts">import { Avatar, AvatarGroup } from '@hareui/vue'const overlaps = ['clip', 'ring'] as const</script><template>  <div class="flex flex-col items-start gap-6">    <div v-for="overlap in overlaps" :key="overlap" class="flex flex-col gap-2">      <p class="text-sm text-muted">{{ overlap }}</p>      <div class="relative inline-flex rounded-xl p-6">        <div aria-hidden="true" class="ag-stripes pointer-events-none absolute inset-0 rounded-xl" />        <div class="relative z-10">          <AvatarGroup :overlap="overlap" size="lg">            <Avatar alt="John" fallback="JD" src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg" />            <Avatar fallback="AB" />            <Avatar alt="Emily" fallback="EC" src="https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg" />            <Avatar fallback="SM" />            <template #count>+2</template>          </AvatarGroup>        </div>      </div>    </div>  </div></template><style>@keyframes ag-stripes {  to { background-position: 24px 24px; }}.ag-stripes {  background-color: color-mix(in oklab, var(--danger) 6%, var(--surface));  background-image: repeating-linear-gradient(    -45deg,    transparent 0 10px,    color-mix(in oklab, var(--danger) 12%, transparent) 10px 11px,    transparent 11px 21px,    color-mix(in oklab, var(--danger) 28%, transparent) 21px 22px  );  background-size: 24px 24px;  animation: ag-stripes 2.8s linear infinite;}</style>

Customization

Tailwind CSS

JDKWEC+3
Assignees
<script setup lang="ts">import { Avatar, AvatarGroup } from '@hareui/vue'import { Icon } from '@iconify/vue'const assignees = [  { id: 1, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg', name: 'John Doe' },  { id: 2, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg', name: 'Kate Wilson' },  { id: 3, image: 'https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg', name: 'Emily Chen' },] as constconst initials = (name: string) => name.split(' ').map(part => part[0]).join('')</script><template>  <div class="inline-flex items-center gap-2.5 rounded-full border border-border/70 bg-surface/95 py-1 pr-3 pl-1 shadow-sm ring-1 ring-black/[0.04] dark:border-border/80 dark:bg-surface/90 dark:ring-white/10">    <AvatarGroup      aria-label="Assignees"      class="[--avatar-group-overlap:0.7rem] [--avatar-group-seam:2px]"      overlap="clip"      role="group"      size="sm"    >      <Avatar v-for="user in assignees" :key="user.id" :alt="user.name" :fallback="initials(user.name)" :src="user.image" />      <Avatar>        <template #fallback>          <Icon class="size-4 shrink-0" icon="gravity-ui:person" />        </template>      </Avatar>      <template #count>+3</template>    </AvatarGroup>    <span class="text-sm font-medium text-foreground">Assignees</span>  </div></template>

Global theme override

Override the avatarGroup theme app-wide with createHareUI:

app.use(createHareUI({
  ui: {
    avatarGroup: {
      slots: {
        base: '[--avatar-group-overlap:0.75rem] [--avatar-group-seam:2px]',
        count: 'font-semibold',
      },
    },
  },
}))

Styling Reference

ui keydata-slotDescription
baseavatar-groupBase avatar group container
countavatar-group-countOverflow count avatar

HeroUI's modifier classes correspond to props (HareUI renders utilities, not BEM classes, so style through class, ui or createHareUI): .avatar-group--grid → grid, .avatar-group--clip / .avatar-group--ring → overlap.

Stacked layout uses negative margin between siblings (--avatar-group-overlap, default 0.5rem). Clip mode masks overlapped avatars (not the last child / count) and optically nudges fallback glyphs; ring mode uses a thin box-shadow outline via --background.

API Reference

Props

PropTypeDefaultDescription
size"md" | "sm" | "lg""md"Size applied to child Avatars (and the overflow count) that don't set size.
color"default" | "accent" | "success" | "warning" | "danger"-Color applied to child Avatars (and the overflow count) that don't set color.
variant"default" | "soft"-Variant applied to child Avatars (and the overflow count) that don't set variant.
maxnumber-Maximum number of Avatars to render; the rest are counted in a +N avatar. Omit to show all.
gridbooleanfalseWrapping grid layout without overlap.
overlap"clip" | "ring""clip"Stacked overlap style: crescent clip or box-shadow ring. Ignored with grid.
uiComponentSlots<{ slots: { base: string; count: string; }; variants: { grid: { true: { base: string; }; false: { base: string; }; }; overlap: { clip: {}; ring: {}; }; }; compoundVariants: ({ grid: boolean; overlap: string; class: { base: string; }; } | { grid: boolean; overlap: string; class: { base: string[]; }; })[]; defaultVariants: { grid: boolean; overlap: string; }; }>-Per-slot class overrides.

Slots

SlotPropsDescription
defaultanyThe Avatar components. Only direct Avatar children inherit size, color and variant.
countanyContent of an explicit overflow-count avatar (e.g. +9 for a known total). It is not truncated by max and replaces the automatic count.

Note

  • size, color and variant are passed to direct Avatar children only, and only where the Avatar doesn't set them
  • max truncates visible avatars when set and auto-counts the remaining ones; omit to show all
  • For a known total (e.g. from the server), use the #count slot. It is not truncated by max and replaces the automatic count
  • grid disables overlap and uses a wrapping grid layout instead
  • overlap defaults to "clip" (crescent seam); use "ring" for the solid outline; no effect with grid
  • No default role="group". When the stack is meaningful (assignees, viewers), pass role="group" with aria-label or aria-labelledby

On this page

No Headings