Skip to content
Brok UI

Loading…

No results

Avatar

Open source Core

User avatars — a single image/initials avatar and an overlapping avatar group with an overflow count.

Version
v1.3.1
Stability
stable
License
MIT
Related
Avatar

Avatar Group — An overlapping stack of avatars with a "+N" overflow chip that gently fans apart on hover. Pure CSS — no JavaScript.

Preview

Ava Chen Bram Diaz Cleo Park DR Dev Rao ⁦+1⁩ 1 more person
previews.components.avatar-group.default.blade.php 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>

Installation

terminal
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.

  • blade resources/views/components/ui/avatar-group.blade.php
Registry dependencies
avatar
Packages
composer: jml/brok:^0.2

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.

avatar-group.md
# 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

monogram.blade.php Blade
<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>
names.blade.php Blade
<x-ui.avatar-group
    list-names
    :max="3"
    :avatars="['Ava Chen', 'Bram Diaz', 'Cleo Park', 'Dev Rao', 'Esme Liu']"
/>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
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.

avatar-group

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

Person or entity representation

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
root
Theming hooks
avatar-group

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
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-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, 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 under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

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.

resources/views/components/ui/avatar-group.blade.php Blade
@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