Avatar Group
Overview
The Avatar Group lays out multiple Avatar items in an overlapping stack — useful for showing the members of a team, project, or conversation in a compact space. The useAvatarGroupRecipe() composable creates a recipe that overlaps its avatar children, draws a ring (in the page background color) around each so they read as separate, and exposes a size axis that controls the overlap amount.
The recipe coordinates its children with CSS only — it targets > .avatar items directly, so any standard Avatar becomes a stack member with no extra wiring. A trailing "+N" overflow indicator is simply another avatar.
Why use the Avatar Group recipe?
The Avatar Group recipe helps you:
- Show many people in little space: Overlap avatars into a compact, scannable stack.
- Keep avatars legible: Each item gets a background-colored ring so overlapping edges stay distinct.
- Scale the overlap: A size axis matches the avatars' size and tightens or loosens the overlap accordingly.
- Handle overflow: Add a "+N" count as a trailing neutral avatar to summarize hidden members.
Usage
Register the recipes
Register the Avatar Group recipe alongside the Avatar recipe — the group styles the .avatar children it contains:
import { styleframe } from 'virtual:styleframe';
import { useAvatarRecipe, useAvatarGroupRecipe } from '@styleframe/theme';
const s = styleframe();
const avatar = useAvatarRecipe(s);
const avatarGroup = useAvatarGroupRecipe(s);
export default s;
Build the component
Wrap a set of avatars with the avatarGroup() class. Give the group and its avatars a matching size so the overlap is proportional:
import { avatarGroup, type AvatarGroupProps } from "virtual:styleframe";
export function AvatarGroup({
children,
...variants
}: AvatarGroupProps & { children?: React.ReactNode }) {
return <div className={avatarGroup(variants)}>{children}</div>;
}
See it in action
Sizes
The group's size controls the overlap amount and should match the size of the avatars inside it. Larger avatars overlap by a larger margin.
| Size | Overlap |
|---|---|
xs | -0.375rem |
sm | -0.5rem |
md | -0.625rem |
lg | -0.75rem |
xl | -1rem |
Overflow count
When there are more members than you want to show, render a trailing "+N" avatar to summarize the rest. It is just another .avatar (neutral, soft) inside the group, so it inherits the ring and overlap automatically.
<div :class="avatarGroup({ size: 'md' })">
<span :class="avatar({ size: 'md' })"><img src="/users/ada.jpg" alt="Ada" /></span>
<span :class="avatar({ size: 'md' })"><img src="/users/alan.jpg" alt="Alan" /></span>
<span :class="avatar({ size: 'md', color: 'neutral', variant: 'soft' })">+3</span>
</div>
Accessibility
- Label the group with
aria-label(e.g.aria-label="Project members") so its purpose is announced. - Keep each avatar's
altmeaningful — the names are the content; the stacking is purely visual. - Describe the overflow in text (e.g.
aria-label="3 more members") rather than relying on the "+N" visual alone.
Customization
Overriding Defaults
const avatarGroup = useAvatarGroupRecipe(s, {
defaultVariants: { size: 'lg' },
});
Filtering Variants
const avatarGroup = useAvatarGroupRecipe(s, {
filter: {
size: ['sm', 'md', 'lg'],
},
});
@color.background, so overlapping avatars are separated by a cut-out that adapts to light and dark mode automatically.API Reference
useAvatarGroupRecipe(s, options?)
Creates the avatar group recipe. Registers selectors that add a ring to each > .avatar, lift the hovered avatar, and apply a per-size negative margin for the overlap.
Parameters:
| Parameter | Type | Description |
|---|---|---|
s | Styleframe | The Styleframe instance |
options | DeepPartial<RecipeConfig> | Optional overrides for the recipe configuration |
options.base | VariantDeclarationsBlock | Custom base styles |
options.variants | Variants | Custom variant definitions |
options.defaultVariants | Record<keyof Variants, string> | Default variant values |
options.filter | Record<string, string[]> | Limit which variant values are generated |
Variants:
| Variant | Options | Default |
|---|---|---|
size | xs, sm, md, lg, xl | md |
Best Practices
- Match the group
sizeto the avatars inside it so the overlap is proportional. - Cap the stack at a handful of avatars and use the "+N" overflow for the rest.
- Order by relevance: the first avatar sits on top of the stack visually — lead with the most important member.
- Use images for distinct people; the ring keeps overlapping faces legible.
FAQ
-<size> marker class (e.g. .avatar-group.-md). A setup selector applies a per-size negative margin-left to every .avatar except the first, pulling them into an overlapping stack.border in @color.background) to each > .avatar so overlapping edges read as separate. Because the avatar uses box-sizing: border-box, the ring doesn't change the avatar's footprint.avatar({ color: 'neutral', variant: 'soft', size }) with +N as its content as the last child. It inherits the ring and overlap from the group.Avatar
A user avatar with an image and an automatic initials fallback — circular or square, with an optional status badge — built as a multi-part recipe system.
Card
A structured container component for grouping related content with header, body, and footer sections. Supports multiple colors, visual styles, and sizes through the recipe system.