GrAvatar
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 fromname, the slot; - a presence status is needed — a dot on the avatar instead of a separate marker beside it;
- there are several participants —
GrAvatarGroupcollects them into a row with overlap and a counter.
When to take something else
| Need | Take |
|---|---|
| The image has to be examined | GrImageViewer |
| An icon is shown, not a person | GrIcon |
| A counter on top of a control is needed | GrBadgeWrap |
| A status as text | GrBadge |
What gets shown
The fallback order: src → fallbackSrc → 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 (xs…lg, 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…
<GrAvatar />Install
npm i @feugene/granularityImport
import { GrAvatar } from '@feugene/granularity/components/GrAvatar'API
Props
| Prop | Type | default | Description |
|---|---|---|---|
size | GrSizeWithPx | undefined | undefined | The 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. |
name | string | undefined | undefined | The name of the participant: it gives the initials and the accessible name of the avatar. |
src | string | undefined | undefined | — |
fallbackSrc | string | undefined | undefined | A fallback picture: shown when `src` did not load. |
alt | string | undefined | undefined | — |
shape | GrAvatarShape | undefined | undefined | — |
status | "online" | "offline" | "busy" | "away" | undefined | undefined | The status of the participant. The dot is decorative — a hidden label goes beside it. |
Slots
| Slot | Type | Description |
|---|---|---|
default | any | Content instead of the picture and the initials. |
Examples 3
Sizes and circle/square shapes
<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
<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
<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>