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.2.0
Stability
stable
License
MIT
Related
Avatar Group
1 of 2

Avatar — User avatar with image and fallback rendering, size and shape variants.

Preview

JD Jane Doe AB Alex Brown CD Casey Doyle
Size
Shape
Tone
previews.components.avatar.default.blade.php Blade
<div class="flex items-center gap-3">
    <x-ui.avatar size="sm" fallback="JD" alt="{{ __('Jane Doe') }}" />
    <x-ui.avatar fallback="AB" alt="{{ __('Alex Brown') }}" />
    <x-ui.avatar size="lg" fallback="CD" alt="{{ __('Casey Doyle') }}" />
</div>

Size options

Xs Current
Sm Current
Md Current
Lg Current
Xl Current

Shape options

Null Current
Circle Current
Rounded Current
Square Current
Tile Current

Tone options

Auto Current
0 Current
1 Current
2 Current
3 Current
4 Current

Installation

terminal
php artisan ui:add avatar

Registry contract

php artisan ui:add avatar 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.blade.php
Registry dependencies
None — installs on its own.
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.md
# Brok UI: Avatar (`avatar`)

User avatar with image and fallback rendering, size and shape variants.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add avatar
```

## Usage

```blade
<div class="flex items-center gap-3">
    <x-ui.avatar size="sm" fallback="JD" alt="{{ __('Jane Doe') }}" />
    <x-ui.avatar fallback="AB" alt="{{ __('Alex Brown') }}" />
    <x-ui.avatar size="lg" fallback="CD" alt="{{ __('Casey Doyle') }}" />
</div>
```

## Props

- `src` (mixed|null, default `null`) — Image URL; when omitted the fallback content renders instead.
- `alt` (string, default ``) — Accessible name for the image; an empty string marks the avatar as decorative.
- `size` (md|xs|sm|lg|xl, default `md`) — Square size token from xs to xl.
- `shape` (string, default `null`) — Outline shape: circle, rounded, square, or tile (size-scaled rounded square). When omitted it follows kind (company: tile) and otherwise circle.
- `fallback` (mixed|null, default `null`) — Content shown when src is empty, typically initials.
- `name` (string|null, default `null`) — Opt-in name mode: derives 1-2 initials (first and last word, Unicode-aware, mb_ functions) and a tint the name always hashes to; labels the avatar with the name unless alt or decorative says otherwise; a src that fails to load shows the initials.
- `kind` (person|company|null, default `null`) — Default outline: person is a circle, company a rounded square (shape tile, radius scaled to the size). An explicit shape wins.
- `tone` (auto|neutral, default `auto`) — auto tints a named avatar with one of five chart-hue pairs (bg-chart-N/20 text-foreground); neutral keeps bg-muted. Without name the avatar is always neutral.
- `decorative` (bool, default `false`) — Hides the avatar from assistive tech (aria-hidden) even when alt or name is set; use it when a visible name sits next to the avatar.

## Use when

- Use to visually represent people or entities in collaborative or social contexts.
- Representing a single person or entity with a photo, falling back to initials or an icon when no image is available.
- Sizing and shaping a face consistently across lists, headers, and cards.

## Avoid when

- Do not rely on avatars alone when names or roles are needed for disambiguation.
- Showing several people at once; use avatar-group so the faces overlap and collapse into a count.

## Anti-patterns

- Relying on an avatar alone for identity

## Rules

- Use the `<brok:avatar>` tag (or `<x-ui.avatar>`) 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
- Registry JSON (files, props, contract): https://brokui.dev/r/open/avatar.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

Examples

group.blade.php Blade
<div class="flex items-center">
    <div class="flex -space-x-3 rtl:space-x-reverse">
        <x-ui.avatar fallback="SR" alt="{{ __('Sofia Rodrigues') }}" class="ring-2 ring-background" />
        <x-ui.avatar fallback="LC" alt="{{ __('Liam Chen') }}" class="ring-2 ring-background" />
        <x-ui.avatar fallback="AM" alt="{{ __('Amir Malik') }}" class="ring-2 ring-background" />
        <x-ui.avatar fallback="JD" alt="{{ __('Jane Doe') }}" class="ring-2 ring-background" />
        <x-ui.avatar fallback="+5" alt="{{ __('5 more members') }}" class="ring-2 ring-background bg-accent text-accent-foreground" />
    </div>
</div>
image-fallback.blade.php Blade
<div class="flex flex-wrap items-center gap-3">
    <x-ui.avatar kind="company" name="Brok UI" src="/favicon-96x96.png" data-test="loaded" />
    <x-ui.avatar kind="company" name="Missing Logo Ltd" src="/images/missing-logo.png" data-test="broken-company" />
    <x-ui.avatar name="Ava Chen" src="/images/missing-photo.jpg" data-test="broken-person" />
    <x-ui.avatar name="Ava Chen" src="/images/missing-photo.jpg" decorative data-test="decorative" />
    <x-ui.avatar name="Ava Chen" data-test="reference" />
</div>
kinds.blade.php Blade
<div class="flex flex-wrap items-center gap-4">
    @foreach (['xs', 'sm', 'md', 'lg', 'xl'] as $size)
        <div class="flex items-center gap-2">
            <x-ui.avatar :size="$size" kind="person" name="Ava Chen" />
            <x-ui.avatar :size="$size" kind="company" name="Northwind Traders" />
        </div>
    @endforeach
</div>
long-content.blade.php Blade
<div class="flex max-w-xs flex-col gap-3">
    <div class="flex min-w-0 items-center gap-3">
        <x-ui.avatar kind="company" name="The International Consolidated Airlines Group of Very Long Names" decorative />
        <span class="min-w-0 truncate text-sm text-foreground">{{ __('The International Consolidated Airlines Group of Very Long Names') }}</span>
    </div>
    <div class="flex min-w-0 items-center gap-3">
        <x-ui.avatar name="Maximiliane-Theodora von Hohenzollern-Sigmaringen" decorative />
        <span class="min-w-0 truncate text-sm text-foreground">{{ __('Maximiliane-Theodora von Hohenzollern-Sigmaringen') }}</span>
    </div>
</div>
monogram.blade.php Blade
<div class="flex flex-wrap items-center gap-3">
    <x-ui.avatar name="Ava Chen" />
    <x-ui.avatar name="Bram van Dijk" />
    <x-ui.avatar name="Émile Zola" />
    <x-ui.avatar name="Łukasz" />
    <x-ui.avatar name="山田 太郎" />
    <x-ui.avatar name="Dev Rao" tone="neutral" />
</div>
shapes.blade.php Blade
<div class="flex flex-wrap items-center gap-3">
    <x-ui.avatar shape="circle" fallback="CI" alt="{{ __('Circle') }}" />
    <x-ui.avatar shape="rounded" fallback="RO" alt="{{ __('Rounded') }}" />
    <x-ui.avatar shape="square" fallback="SQ" alt="{{ __('Square') }}" />
</div>
sizes.blade.php Blade
<div class="flex flex-wrap items-center gap-3">
    <x-ui.avatar size="xs" fallback="XS" alt="{{ __('Extra small') }}" />
    <x-ui.avatar size="sm" fallback="SM" alt="{{ __('Small') }}" />
    <x-ui.avatar size="md" fallback="MD" alt="{{ __('Medium') }}" />
    <x-ui.avatar size="lg" fallback="LG" alt="{{ __('Large') }}" />
    <x-ui.avatar size="xl" fallback="XL" alt="{{ __('Extra large') }}" />
</div>
status.blade.php Blade
<div class="flex flex-wrap items-center gap-4">
    <span class="relative inline-flex">
        <x-ui.avatar fallback="ON" alt="{{ __('Online user') }}" />
        <span class="absolute bottom-0 end-0 size-3 rounded-full bg-success ring-2 ring-background" aria-hidden="true"></span>
        <span class="sr-only">{{ __('Online') }}</span>
    </span>

    <span class="relative inline-flex">
        <x-ui.avatar fallback="AW" alt="{{ __('Away user') }}" />
        <span class="absolute bottom-0 end-0 size-3 rounded-full bg-warning ring-2 ring-background" aria-hidden="true"></span>
        <span class="sr-only">{{ __('Away') }}</span>
    </span>

    <span class="relative inline-flex">
        <x-ui.avatar fallback="BS" alt="{{ __('Busy user') }}" />
        <span class="absolute bottom-0 end-0 size-3 rounded-full bg-destructive ring-2 ring-background" aria-hidden="true"></span>
        <span class="sr-only">{{ __('Busy') }}</span>
    </span>

    <span class="relative inline-flex">
        <x-ui.avatar fallback="OF" alt="{{ __('Offline user') }}" />
        <span class="absolute bottom-0 end-0 size-3 rounded-full bg-muted-foreground ring-2 ring-background" aria-hidden="true"></span>
        <span class="sr-only">{{ __('Offline') }}</span>
    </span>
</div>
with-badge.blade.php Blade
<div class="flex flex-wrap items-center gap-4">
    <span class="relative inline-flex">
        <x-ui.avatar size="lg" fallback="SR" alt="{{ __('Sofia Rodrigues') }}" />
        <span class="absolute -bottom-1 -end-1 inline-flex size-6 items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background">
            <svg class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                <path d="M20 6 9 17l-5-5" />
            </svg>
        </span>
        <span class="sr-only">{{ __('Verified') }}</span>
    </span>

    <span class="relative inline-flex">
        <x-ui.avatar size="lg" fallback="AM" alt="{{ __('Amir Malik') }}" />
        {{-- A label badge sits half below the avatar's edge, so the initials stay readable. --}}
        <x-ui.badge variant="outline" size="sm" class="absolute bottom-0 end-1/2 translate-x-1/2 translate-y-1/2 rtl:-translate-x-1/2 bg-background">
            {{ __('Pro') }}
        </x-ui.badge>
    </span>
</div>
with-icon.blade.php Blade
<div class="flex flex-wrap items-center gap-3">
    <x-ui.avatar size="sm" alt="{{ __('User placeholder') }}">
        <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
            <circle cx="12" cy="7" r="4" />
        </svg>
    </x-ui.avatar>
    <x-ui.avatar alt="{{ __('User placeholder') }}">
        <svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
            <circle cx="12" cy="7" r="4" />
        </svg>
    </x-ui.avatar>
    <x-ui.avatar size="lg" shape="rounded" alt="{{ __('User placeholder') }}">
        <svg class="size-6" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
            <path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
            <circle cx="12" cy="7" r="4" />
        </svg>
    </x-ui.avatar>
</div>
with-name.blade.php Blade
<div class="flex items-center gap-3">
    <x-ui.avatar size="lg" fallback="SR" alt="{{ __('Sofia Rodrigues') }}" />
    <div class="flex flex-col">
        <span class="text-sm font-medium text-foreground">{{ __('Sofia Rodrigues') }}</span>
        <span class="text-xs text-muted-foreground">sofia@example.com</span>
    </div>
</div>

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
src mixed | null null Image URL; when omitted the fallback content renders instead.
alt string Accessible name for the image; an empty string marks the avatar as decorative.
size md | xs | sm | lg | xl md Square size token from xs to xl.
shape string null Outline shape: circle, rounded, square, or tile (size-scaled rounded square). When omitted it follows kind (company: tile) and otherwise circle.
fallback mixed | null null Content shown when src is empty, typically initials.
name string | null null Opt-in name mode: derives 1-2 initials (first and last word, Unicode-aware, mb_ functions) and a tint the name always hashes to; labels the avatar with the name unless alt or decorative says otherwise; a src that fails to load shows the initials.
kind person | company | null null Default outline: person is a circle, company a rounded square (shape tile, radius scaled to the size). An explicit shape wins.
tone auto | neutral auto auto tints a named avatar with one of five chart-hue pairs (bg-chart-N/20 text-foreground); neutral keeps bg-muted. Without name the avatar is always neutral.
decorative bool false Hides the avatar from assistive tech (aria-hidden) even when alt or name is set; use it when a visible name sits next to the avatar.

Slots

  • default —

Data slots

Stable hooks for CSS overrides and browser tests.

avatar avatar-fallback avatar-image

Behavior

  • Renders the image when src is set; otherwise shows the fallback content, typically initials, inside a muted circle.
  • alt is announced when present; when alt is empty the fallback content is marked decorative and hidden from assistive tech.
  • Name mode (name set) renders the root as role="img" with aria-label (alt, else name) or aria-hidden when decorative; the image is layered on top with alt="" and its ::after pseudo-element shows the initials when the image fails, so no broken-image icon appears and no JavaScript is needed (Chromium and Firefox; Safari shows the tint without initials).
  • The tint index is crc32 of the lower-cased name modulo five, so a name keeps its colour across pages and requests; data-tone exposes it as tint-1..tint-5 or neutral.
  • Without name, kind or decorative the output is unchanged from 1.1.x.
  • 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.
  • Representing a single person or entity with a photo, falling back to initials or an icon when no image is available.
  • Sizing and shaping a face consistently across lists, headers, and cards.

Avoid when

  • Do not rely on avatars alone when names or roles are needed for disambiguation.
  • Showing several people at once; use avatar-group so the faces overlap and collapse into a count.

Use instead

  • Name and role text

Anti-patterns

  • Relying on an avatar alone for identity
Anatomy
root
Theming hooks
avatar avatar-fallback avatar-image

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
  • Pass alt with the person's name whenever the avatar conveys identity; leave it empty only for a purely decorative avatar.
  • Name mode is labelled by default (role="img", aria-label = alt or name); pass decorative when the name is already visible beside it, so it is not announced twice.
  • Monogram text is the foreground token on a 20% chart tint, above 4.5:1 in light and dark.
  • 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.blade.php Blade
@props([
    'src' => null,
    'alt' => '',
    'size' => 'md',
    'shape' => null,
    'fallback' => null,
    'name' => null,
    'kind' => null,
    'tone' => 'auto',
    'decorative' => false,
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');

    $sizes = [
        'xs' => 'size-6 text-[0.625rem]',
        'sm' => 'size-8 text-xs',
        'md' => 'size-10 text-sm',
        'lg' => 'size-12 text-base',
        'xl' => 'size-16 text-lg',
    ];
    $size = isset($sizes[$size]) ? $size : 'md';

    // `kind` picks the default outline: a person is a circle, a company a
    // rounded square (`tile`). An explicit `shape` always wins.
    $kind = in_array($kind, ['person', 'company'], true) ? $kind : null;
    $shape = $shape ?? ($kind === 'company' ? 'tile' : 'circle');

    $shapes = [
        'circle' => 'rounded-full',
        'rounded' => 'rounded-lg',
        'square' => 'rounded-none',
        'tile' => $styles['avatar']['tileRadius'][$size],
    ];

    // Name mode: initials (first and last word, 1-2 letters) and a tint the
    // name always hashes to. Without a name the avatar renders as before.
    $name = filled($name) ? trim((string) $name) : null;
    $monogram = null;
    $toneIndex = null;
    $toneClass = 'bg-muted text-muted-foreground';

    if ($name !== null) {
        $letters = [];
        foreach (preg_split('/\s+/u', $name, -1, PREG_SPLIT_NO_EMPTY) ?: [] as $word) {
            if (preg_match('/[\p{L}\p{N}]/u', $word, $match) === 1) {
                $letters[] = $match[0];
            }
        }
        $picked = count($letters) > 1 ? [$letters[0], $letters[count($letters) - 1]] : $letters;
        $monogram = mb_strtoupper(implode('', $picked));

        if ($tone !== 'neutral') {
            $palette = $styles['avatar']['tones'];
            $toneIndex = crc32(mb_strtolower($name)) % count($palette);
            $toneClass = $palette[$toneIndex];
        }
    }

    $imageFallbackClass = '';
    if ($name !== null) {
        $imageFallbackClass = $styles['avatar']['imageFallbackBase'].' '
            .($toneIndex === null ? $styles['avatar']['imageFallbackNeutral'] : $styles['avatar']['imageFallback'][$toneIndex]);
    }

    $classes = trim('relative inline-flex shrink-0 items-center justify-center overflow-hidden '
        .$toneClass.' '
        .($shapes[$shape] ?? $shapes['circle']).' '
        .$sizes[$size]);

    $label = $decorative ? '' : ($alt !== '' ? (string) $alt : (string) $name);
    $content = $fallback ?? ($slot->isEmpty() ? $monogram : $slot);
    $imageFallback = is_string($fallback) && $fallback !== '' ? $fallback : (string) $monogram;
@endphp

@if ($name === null)
<span
    data-slot="avatar"
    data-size="{{ $size }}"
    data-shape="{{ $shape }}"
    @if ($kind) data-kind="{{ $kind }}" @endif
    @if ($decorative) aria-hidden="true" @endif
    {{ $attributes->merge(['class' => $classes]) }}
>
    @if ($src)
        <img src="{{ $src }}" alt="{{ $decorative ? '' : $alt }}" class="size-full object-cover" />
    @else
        <span class="font-medium" aria-hidden="{{ $alt === '' || $decorative ? 'true' : 'false' }}">{{ $fallback ?? $slot }}</span>
        @if ($alt !== '' && ! $decorative)
            <span class="sr-only">{{ $alt }}</span>
        @endif
    @endif
</span>
@else
{{-- Name mode: the root carries the accessible name (role="img") or is hidden
     when decorative. A broken image paints its pseudo-elements in Chromium
     and Firefox: ::before covers the broken-image glyph, ::after shows the
     tinted initials, so a failed
     load never shows a broken-image icon and needs no JavaScript. --}}
<span
    data-slot="avatar"
    data-size="{{ $size }}"
    data-shape="{{ $shape }}"
    @if ($kind) data-kind="{{ $kind }}" @endif
    data-tone="{{ $toneIndex === null ? 'neutral' : 'tint-'.($toneIndex + 1) }}"
    @if ($label !== '') role="img" aria-label="{{ $label }}" @else aria-hidden="true" @endif
    {{ $attributes->merge(['class' => $classes]) }}
>
    @if ($src)
        <img
            data-slot="avatar-image"
            src="{{ $src }}"
            alt=""
            data-fallback="{{ $imageFallback }}"
            class="absolute inset-0 size-full object-cover {{ $imageFallbackClass }}"
        />
    @else
        <span data-slot="avatar-fallback" class="font-medium" aria-hidden="true">{{ $content }}</span>
    @endif
</span>
@endif

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.2.0
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