GrAvatar

Package: @feugene/granularitycoreGroup: data

Displays a user photo, or initials / a fallback when it is missing.

Machine-translated from the Russian original, not yet reviewed. Read the original

When to take it

  • a person or an entity is recognised by a picture — a list of participants, the author of a comment, the owner of a task;
  • there may be no picture — the fallback runs down a chain: src, fallbackSrc, initials from name, the slot;
  • a presence status is needed — a dot on the avatar instead of a separate marker beside it;
  • there are several participantsGrAvatarGroup collects them into a row with overlap and a counter.

When to take something else

NeedTake
The image has to be examinedGrImageViewer
An icon is shown, not a personGrIcon
A counter on top of a control is neededGrBadgeWrap
A status as textGrBadge

What gets shown

The fallback order: srcfallbackSrc → initials from name → the default slot.

<GrAvatar src="https://cdn.example/u/42.png" fallback-src="/avatar.svg" name="Ada Lovelace" />

A broken link is handled: on @error the component moves to the next option, and the browser does not draw the broken-image icon. Changing src resets the error — a new link does not inherit the fate of the previous one.

While the picture is on its way, its place is held by a skeleton: otherwise a row of avatars flickers with empty circles.

The name and the initials

name provides the initials (the first letters of the first two words) and the accessible name. When there is a picture, the name serves as its alt; without a picture the avatar itself takes the img role and the name — otherwise a circle with initials is empty for a screen reader. alt beats name, the slot beats the initials.

The initials function is available separately: import { initialsFrom } from '@feugene/granularity'.

The type size is computed from the diameter — a third of it, but no less than 10px. The size of an avatar can be an arbitrary number of pixels, so a map across the steps of the scale does not cover it. The rule is one for the initials, the slot text and the “+N” counter of GrAvatarGroup: they stand in the same row, and different proportions would read as a defect.

The status

<GrAvatar name="Ada Lovelace" status="online" />

online | offline | busy | away. The dot is decorative (aria-hidden), and the status is announced with a word from the locale (gr.avatar.status.*): colour on its own carries no meaning.

A row of participants

<GrAvatarGroup :max="3" :total="9" aria-label="Release team">
  <GrAvatar v-for="member in team" :key="member.id" :name="member.name" />
</GrAvatarGroup>

max limits the number of visible avatars, the remainder collapses into “+N”; total is needed when the slot holds only part of the participants. The group is declared role="group", and its name contains both the label and the number hidden — a screen reader says “Release team, and 6 more” rather than a set of nameless pictures.

The size and shape of the group reach the children through context, so the row does not fall apart.

Size and shape

size accepts a step of the scale (xslg, read from GrConfigProvider) or a number of pixels — an avatar has historically had an arbitrary diameter. shape (circle | square) is likewise configured globally through componentDefaults.GrAvatar.shape.

Playground 5

Loading…

Code
<GrAvatar />

Install

npm i @feugene/granularity

Import

import { GrAvatar } from '@feugene/granularity/components/GrAvatar'

API

Props

PropTypedefaultDescription
sizeGrSizeWithPx | undefinedundefinedThe size by the canonical scale (`xs|sm|md|lg`) — then `GrConfigProvider` applies. A number is an escape hatch: an avatar is needed at an arbitrary diameter (24px in a list row, 96px in a profile), and four steps of the scale do not cover that.
namestring | undefinedundefinedThe name of the participant: it gives the initials and the accessible name of the avatar.
srcstring | undefinedundefined
fallbackSrcstring | undefinedundefinedA fallback picture: shown when `src` did not load.
altstring | undefinedundefined
shapeGrAvatarShape | undefinedundefined
status"online" | "offline" | "busy" | "away" | undefinedundefinedThe status of the participant. The dot is decorative — a hidden label goes beside it.

Slots

SlotTypeDescription
defaultanyContent instead of the picture and the initials.

Examples 3

Sizes and circle/square shapes

ADADADAD
QAPMUX

Size Shape
<script setup lang="ts">
import { GrAvatar } from '@feugene/granularity'
</script>

<template>
  <div class="grid gap-4">
    <div class="flex flex-wrap items-center gap-4">
      <GrAvatar :size="32">AD</GrAvatar>
      <GrAvatar :size="40">AD</GrAvatar>
      <GrAvatar :size="56">AD</GrAvatar>
      <GrAvatar :size="72">AD</GrAvatar>
    </div>

    <div class="flex flex-wrap items-center gap-4">
      <GrAvatar :size="40" shape="square">QA</GrAvatar>
      <GrAvatar :size="56" shape="square">PM</GrAvatar>
      <GrAvatar :size="72" shape="square">UX</GrAvatar>
    </div>
  </div>
</template>

Image mode with default-slot fallback

online
Fallback contract
A broken `src` falls back to `fallbackSrc`, then to initials from `name` — the browser never shows its broken-image icon. Without `src` the default slot is rendered as before.

Image Fallback
<script setup lang="ts">
import { GrAvatar, GrCard } from '@feugene/granularity'

const avatarSvg = encodeURIComponent(`
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
    <rect width="96" height="96" fill="#dbeafe" />
    <circle cx="48" cy="36" r="16" fill="#2563eb" opacity="0.18" />
    <path d="M18 80c6-15 18-23 30-23s24 8 30 23" fill="#2563eb" opacity="0.26" />
    <circle cx="48" cy="36" r="13" fill="#2563eb" />
  </svg>
`)

const avatarImageSrc = `data:image/svg+xml;charset=UTF-8,${avatarSvg}`
</script>

<template>
  <div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px] lg:items-center">
    <div class="flex flex-wrap items-center gap-4">
      <GrAvatar :size="40" :src="avatarImageSrc" alt="Alex Doe" />
      <GrAvatar :size="56" :src="avatarImageSrc" alt="Alex Doe" status="online" />
      <GrAvatar :size="72" shape="square" :src="avatarImageSrc" alt="Alex Doe" />

      <!-- Битая ссылка — основной сценарий отказа: показываются инициалы из `name`. -->
      <GrAvatar :size="56" src="/broken-avatar.png" name="Alex Doe" />
      <GrAvatar :size="56" src="/broken-avatar.png" :fallback-src="avatarImageSrc" name="Alex Doe" />
    </div>

    <GrCard class="grid gap-2 p-4 text-sm text-[var(--gr-muted-fg)]">
      <div class="font-semibold text-[var(--gr-fg)]">
        Fallback contract
      </div>
      <div>
        A broken `src` falls back to `fallbackSrc`, then to initials from `name` — the browser never shows its
        broken-image icon. Without `src` the default slot is rendered as before.
      </div>
    </GrCard>
  </div>
</template>

Composition inside user or team rows

ADonline
Alex Doe
Engineering lead
Release squad
ADonlineQAbusySRaway
Стекинг с «+N»: группа объявляет диктору и имя, и число скрытых участников.

Team Row
<script setup lang="ts">
import { GrAvatar, GrAvatarGroup, GrCard } from '@feugene/granularity'

const team = [
  { name: 'Alex Doe', status: 'online' as const },
  { name: 'Quinn Ali', status: 'busy' as const },
  { name: 'Sam Rivera', status: 'away' as const },
  { name: 'Noor Haddad', status: 'offline' as const },
]
</script>

<template>
  <div class="grid gap-3">
    <GrCard class="grid gap-3 p-4">
      <div class="flex items-center gap-3">
        <GrAvatar :size="44" name="Alex Doe" status="online" />
        <div>
          <div class="text-sm font-semibold text-[var(--gr-fg)]">
            Alex Doe
          </div>
          <div class="text-sm text-[var(--gr-muted-fg)]">
            Engineering lead
          </div>
        </div>
      </div>
    </GrCard>

    <GrCard class="grid gap-3 p-4">
      <div class="text-sm font-semibold text-[var(--gr-fg)]">
        Release squad
      </div>
      <GrAvatarGroup :max="3" :total="9" size="md" aria-label="Release squad">
        <GrAvatar v-for="member in team" :key="member.name" :name="member.name" :status="member.status" />
      </GrAvatarGroup>
      <div class="text-sm text-[var(--gr-muted-fg)]">
        Стекинг с «+N»: группа объявляет диктору и имя, и число скрытых участников.
      </div>
    </GrCard>
  </div>
</template>

Component documentationAll components