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 key | data-slot | Description |
|---|---|---|
base | avatar-group | Base avatar group container |
count | avatar-group-count | Overflow 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
| Prop | Type | Default | Description |
|---|---|---|---|
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. |
max | number | - | Maximum number of Avatars to render; the rest are counted in a +N avatar. Omit to show all. |
grid | boolean | false | Wrapping grid layout without overlap. |
overlap | "clip" | "ring" | "clip" | Stacked overlap style: crescent clip or box-shadow ring. Ignored with grid. |
ui | ComponentSlots<{ 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
| Slot | Props | Description |
|---|---|---|
default | any | The Avatar components. Only direct Avatar children inherit size, color and variant. |
count | any | Content 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,colorandvariantare passed to directAvatarchildren only, and only where the Avatar doesn't set themmaxtruncates 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
#countslot. It is not truncated bymaxand replaces the automatic count griddisables overlap and uses a wrapping grid layout insteadoverlapdefaults to"clip"(crescent seam); use"ring"for the solid outline; no effect withgrid- No default
role="group". When the stack is meaningful (assignees, viewers), passrole="group"witharia-labeloraria-labelledby



