Avatar
User avatars — a single image/initials avatar and an overlapping avatar group with an overflow count.
Avatar Group — An overlapping stack of avatars with a "+N" overflow chip that gently fans apart on hover. Pure CSS — no JavaScript.
Preview
<div class="flex min-h-40 w-full items-center justify-center rounded-lg border border-border bg-card p-8">
<x-ui.avatar-group :max="4" />
</div>
Installation
php artisan ui:add avatar-group
Registry contract
php artisan ui:add avatar-group
writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.
-
resources/views/components/ui/avatar-group.blade.php
Use with AI
A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.
# Brok UI: Avatar Group (`avatar-group`)
An overlapping stack of avatars with a "+N" overflow chip that gently fans apart on hover. Pure CSS — no JavaScript.
Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.
## Install
```bash
php artisan ui:add avatar-group
```
## Usage
```blade
<div class="flex min-h-40 w-full items-center justify-center rounded-lg border border-border bg-card p-8">
<x-ui.avatar-group :max="4" />
</div>
```
## Props
- `avatars` (array, default `[…]`) — Array of {src, name, initials} entries, or bare name strings, rendered as overlapping faces.
- `max` (int, default `4`) — Faces shown before the rest collapse into a +N avatar.
- `size` (xs|sm|md|lg|xl, default `md`) — Avatar scale forwarded to every member.
## Use when
- Use to visually represent people or entities in collaborative or social contexts.
- Showing several people who share ownership of something, such as collaborators or assignees, as one compact overlapping stack.
- Collapsing a long list of participants into a fixed number of visible faces plus a +N count.
## Avoid when
- Do not rely on avatars alone when names or roles are needed for disambiguation.
- Only one person needs representing; use avatar on its own.
## Anti-patterns
- Relying on an avatar alone for identity
## Rules
- Use the `<brok:avatar-group>` tag (or `<x-ui.avatar-group>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.
## Links
- Docs: https://brokui.dev/docs/components/avatar-group
- Registry JSON (files, props, contract): https://brokui.dev/r/open/avatar-group.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Examples
<div class="flex flex-col gap-4">
@foreach (['sm', 'md', 'lg'] as $size)
<x-ui.avatar-group
monogram
list-names
:size="$size"
:max="4"
:avatars="[
['name' => 'Ava Chen'],
['name' => 'Northwind Traders', 'kind' => 'company'],
['name' => 'Cleo Park', 'src' => '/images/missing-photo.jpg'],
['name' => 'Dev Rao'],
['name' => 'Esme Liu'],
['name' => 'Farah Haddad'],
]"
/>
@endforeach
</div>
<x-ui.avatar-group
list-names
:max="3"
:avatars="['Ava Chen', 'Bram Diaz', 'Cleo Park', 'Dev Rao', 'Esme Liu']"
/>
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| avatars | array | […] | Array of {src, name, initials} entries, or bare name strings, rendered as overlapping faces. |
| max | int | 4 | Faces shown before the rest collapse into a +N avatar. |
| size | xs | sm | md | lg | xl | md | Avatar scale forwarded to every member. |
Slots
default—
Data slots
Stable hooks for CSS overrides and browser tests.
Behavior
- Renders up to max avatars overlapping with a background ring, and folds any remainder into a trailing +N avatar.
- Hovering the group fans the faces out slightly via a CSS margin transition.
- listNames joins names as \":names and :last\" through __(), so the conjunction is translatable.
- Each entry may carry kind (person|company), forwarded to avatar; with monogram the entry name drives the initials and tint unless initials is set.
- Collapses overflow into a localized count avatar.
- Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.
Guidance
Represent a person or entity visually.
Use when
- Use to visually represent people or entities in collaborative or social contexts.
- Showing several people who share ownership of something, such as collaborators or assignees, as one compact overlapping stack.
- Collapsing a long list of participants into a fixed number of visible faces plus a +N count.
Avoid when
- Do not rely on avatars alone when names or roles are needed for disambiguation.
- Only one person needs representing; use avatar on its own.
Use instead
- Name and role text
Anti-patterns
- Relying on an avatar alone for identity
- Anatomy
- Theming hooks
Accessibility
- Keyboard
- No component-owned keyboard interaction; native element behavior applies.
- Focus
none
- The root carries role=group and an aria-label, defaulting to 'People', so the collection is announced as one unit.
- With listNames the root aria-label lists everyone, including people folded into +N, which the default People label and the per-face alt text do not.
- Semantic HTML and a stable
data-slotattribute for styling and scripting hooks. - Focus-visible rings use the
ringtoken, so keyboard focus is always visible. - Disabled and invalid states are conveyed to assistive tech, not by color alone.
- Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
- Labels go through
__()and layout uses logical properties (ms-*,text-start), so it mirrors underdir="rtl"— flip the preview to RTL to confirm. - Dark mode uses the same semantic tokens under the
darkclass; high contrast follows forced-color system tokens.
Livewire
Livewire can update this component through forwarded wire:* attributes.
Source
The exact, editable file ui:add writes
into your app. Previews render this same code; there are no preview-only components.
@props([
// Each entry: ['src' => url|null, 'name' => 'Full Name', 'initials' => 'FN'].
// A bare string is treated as a name (initials avatar); `initials` overrides
// the letters derived from the name.
'avatars' => [
['src' => 'https://picsum.photos/seed/ava/80/80', 'name' => 'Ava Chen'],
['src' => 'https://picsum.photos/seed/bram/80/80', 'name' => 'Bram Diaz'],
['src' => 'https://picsum.photos/seed/cleo/80/80', 'name' => 'Cleo Park'],
['src' => null, 'name' => 'Dev Rao'],
['src' => 'https://picsum.photos/seed/esme/80/80', 'name' => 'Esme Liu'],
],
// Maximum faces to show before collapsing the rest into a "+N" chip.
'max' => 4,
'size' => 'md',
// Accessible name of the group; defaults to "People".
'label' => null,
// Opt-in: when no label is given, name the group with every person, hidden
// ones included ("Ava Chen, Bram Diaz and Esme Liu"), and hide the faces
// and the +N chip from assistive tech so nothing is announced twice.
'listNames' => false,
// Opt-in: render each face in avatar's name mode (tinted monogram, and the
// monogram again when an image fails to load).
'monogram' => false,
])
@php
// Normalise: allow a plain string per entry as the name.
$people = array_map(
fn ($a) => is_array($a) ? $a : ['src' => null, 'name' => (string) $a],
array_values($avatars),
);
$visible = array_slice($people, 0, (int) $max);
$overflow = max(0, count($people) - (int) $max);
$initials = function (string $name): string {
$parts = preg_split('/\s+/', trim($name)) ?: [];
return mb_strtoupper(collect($parts)->filter()->take(2)
->map(fn ($p) => mb_substr($p, 0, 1))->implode(''));
};
$names = collect($people)->pluck('name')->map(fn ($n) => trim((string) $n))->filter()->values();
$groupLabel = $label ?? __('People');
if ($listNames && $label === null && $names->isNotEmpty()) {
$groupLabel = $names->count() === 1
? $names->first()
: __(':names and :last', ['names' => $names->slice(0, -1)->implode(', '), 'last' => $names->last()]);
}
// A ring in the surface colour separates overlapping faces; the fan-out is a
// pure-CSS margin transition driven by `group-hover` (RTL-safe via `ms-*`).
$faceBase = 'ring-2 ring-background transition-[margin] duration-300 ease-out motion-reduce:transition-none';
$overlap = '-ms-3 group-hover:ms-0.5';
// A left-to-right isolate keeps "+3" in that order on an RTL page, where the
// leading "+" would otherwise move after the number. dir="ltr" on the chip
// would also flip its -ms-3 overlap.
$overflowLabel = "\u{2066}+{$overflow}\u{2069}";
@endphp
<div
data-slot="avatar-group"
role="group"
aria-label="{{ $groupLabel }}"
{{ $attributes->merge(['class' => 'group flex items-center']) }}
>
@foreach ($visible as $i => $person)
<x-ui.avatar
:size="$size"
:src="$person['src'] ?? null"
:alt="$person['name'] ?? ''"
:fallback="$person['initials'] ?? ($monogram ? null : $initials($person['name'] ?? ''))"
:name="$monogram && filled($person['name'] ?? null) ? $person['name'] : null"
:kind="$person['kind'] ?? null"
:decorative="(bool) $listNames"
class="{{ $i === 0 ? $faceBase : $faceBase.' '.$overlap }}"
/>
@endforeach
@if ($overflow > 0)
<x-ui.avatar
:size="$size"
:alt="trans_choice(':count more person|:count more people', $overflow, ['count' => $overflow])"
:fallback="$overflowLabel"
:decorative="(bool) $listNames"
class="{{ $faceBase.' '.$overlap }} font-medium"
/>
@endif
</div>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.3.1 - Status
- Stable
- License
-
open - Accessibility reviewed
- No review date recorded
- Last breaking change
- No date recorded
- Deprecation
- Not deprecated
- Contract
-
v6 - Foundation
-
≥ 1.0.0