Designer view
Avatar
A graphic representation of a user, organisation, or entity — image, initials, or icon — rendered in a bounded surface (circle or square). Carries a fallback chain when the primary representation is missing or fails to load. Often paired with a status indicator and composed into avatar-groups for collaborator lists.
Also called User photo Profile picture Pic
When to use
Use
For representing a user, organisation, or entity in lists, headers, comment threads, attribution lines, and group-membership displays. Pair with a label or name when the avatar is the only marker; pair with the entity's display name when the avatar is decorative reinforcement. The status-indicator slot communicates presence (online / offline / away / busy) for collaborator-aware contexts.
Avoid
For decorative thumbnails that do not represent an entity — that is `Tile` (image-led grid item) or a plain image. For organisation branding above content — that is brand imagery, not avatar. For icon-only buttons — that is `Button` with `Icon`, not Avatar repurposed.
Versus related
- icon
`Icon` is the SVG-glyph primitive that may sit inside Avatar as the fallback-icon slot; Avatar is the entity- representation surface. Icon represents an action or concept; Avatar represents an entity.
- tile
`Tile` is an image-led grid item for media galleries; Avatar is a bounded entity-representation surface, typically inline with text rather than in a grid. Tile dimensions vary with media aspect ratio; Avatar locks to a square or circle in a canonical size.
- avatar-group
`Avatar Group` composes multiple Avatar instances with a bounded `max` count and an overflow indicator; Avatar is the single-entity primitive that Avatar Group composes. When the count is one, render Avatar directly — Avatar Group with one child is an anti-pattern.
Avatar is the canonical user-or-entity-representation primitive — a bounded surface (circle or square) that renders an image, initials, or fallback icon for the represented entity. The fallback chain (image → initials → icon) is deterministic by canonical contract; implementations that pick a different order ship inconsistent surfaces under network failure or missing-data. Five sizes (xs / sm / md / lg / xl), two shapes (circle / square), and an optional status indicator (online / offline / away / busy). The reference documents the slot anatomy, the fallback contract, the accessible- name rules across the three render-modes, and the divergence from Icon and Tile.
Implementations
How specific libraries realise the canonical anatomy. Each entry records the deltas between the canon and the library's surface.
Avatar import * as Avatar from '@radix-ui/react-avatar';
<Avatar.Root className="AvatarRoot"> <Avatar.Image className="AvatarImage" src="https://example.com/photo.jpg" alt="Alex Black" onLoadingStatusChange={(status) => console.log(status)} /> <Avatar.Fallback className="AvatarFallback" delayMs={600}> AB </Avatar.Fallback></Avatar.Root>Divergence
| From | Type | → To | Rationale |
|---|---|---|---|
anatomy[initials] | reshaped | Avatar.Fallback (accepts any children — initials text, icon element, or any React node) | Canonical anatomy separates initials and fallback-icon as two distinct slots in an ordered chain. Radix collapses both into a single Avatar.Fallback that accepts arbitrary children, leaving the initials-vs-icon distinction entirely to the consumer. The library manages only whether Fallback is visible (image loading or error), not which fallback tier is active. Consumers who need the canonical two-rung chain must compose two elements inside a single Avatar.Fallback or conditionally choose content themselves. Source: https://www.radix-ui.com/primitives/docs/components/avatar (fetched 2026-05-05) |
anatomy[fallback-icon] | omitted | — | Radix has no separate fallback-icon slot. Both the initials rung and the icon rung of the canonical fallback chain map to the same Avatar.Fallback component. Distinguishing between them is the consumer's responsibility via conditional rendering inside Fallback's children prop. Source: https://raw.githubusercontent.com/radix-ui/primitives/main/packages/react/avatar/src/avatar.tsx (fetched 2026-05-05) |
anatomy[status-indicator] | omitted | — | Radix Avatar has no status-indicator slot. Presence states (online, offline, away, busy) are not modeled in the library at all. Consumers who need a status badge must position a separate element relative to Avatar.Root using CSS; Radix provides no anchor, ARIA wiring, or accessible-label slot for this purpose. Source: https://www.radix-ui.com/primitives/docs/components/avatar (fetched 2026-05-05) |
axes.variants[circle] | omitted | — | Radix Avatar.Root extends a plain `<span>` with no shape props. The circle shape (border-radius: 50%) is a consumer CSS concern. Radix does not ship a `shape` prop or a variant enumeration. Source: https://www.radix-ui.com/primitives/docs/components/avatar#api-reference (fetched 2026-05-05) |
axes.variants[square] | omitted | — | Same as circle: Radix has no shape variant. Square shape is consumer CSS. Source: https://www.radix-ui.com/primitives/docs/components/avatar#api-reference (fetched 2026-05-05) |
axes.properties[size] | omitted | — | Radix Avatar has no size prop. Dimensions are set by consumer CSS on Avatar.Root. The canonical xs/sm/md/lg/xl size scale is a design-system convention layered above the primitive, not a primitive concern. Source: https://www.radix-ui.com/primitives/docs/components/avatar#api-reference (fetched 2026-05-05) |
axes.properties[status] | omitted | — | Radix Avatar has no concept of presence status. The online/offline/away/ busy enum does not exist in the library. Consumers must implement status as a separate overlay element. Source: https://www.radix-ui.com/primitives/docs/components/avatar#api-reference (fetched 2026-05-05) |
axes.properties[hasStatusIndicator] | omitted | — | No status indicator exists in Radix Avatar; this boolean control has no corresponding surface in the library. Source: https://www.radix-ui.com/primitives/docs/components/avatar#api-reference (fetched 2026-05-05) |
axes.states.data[initials-fallback] | reshaped | onLoadingStatusChange status='error' or status='loading' with consumer-chosen Fallback children | Canonical defines `initials-fallback` as a discrete data state signalling that the initials rung is active. Radix models loading state as ImageLoadingStatus ('idle' | 'loading' | 'loaded' | 'error') surfaced via Avatar.Image's `onLoadingStatusChange` callback. Which visual content (initials or icon) appears inside Avatar.Fallback is driven entirely by the consumer, not the library. There is no Radix-native concept of "initials-fallback" vs "icon-fallback". Source: https://raw.githubusercontent.com/radix-ui/primitives/main/packages/react/avatar/src/avatar.tsx (fetched 2026-05-05) |
axes.states.data[icon-fallback] | omitted | — | Radix has no icon-fallback state. As with initials-fallback, Fallback visibility is binary (shown or hidden based on image loading status); the content tier within Fallback is not tracked as a library state. Source: https://raw.githubusercontent.com/radix-ui/primitives/main/packages/react/avatar/src/avatar.tsx (fetched 2026-05-05) |
events[imageLoad] | reshaped | onLoadingStatusChange(status='loaded') on Avatar.Image | Canonical `imageLoad` fires as a discrete event with payload `{ src, durationMs }`. Radix uses a single `onLoadingStatusChange` callback prop on Avatar.Image that fires on every status transition ('idle' → 'loading' → 'loaded' or 'error'). To match the canonical imageLoad contract, consumers filter for `status === 'loaded'`. The `durationMs` payload field has no Radix equivalent — consumers must record the load-start timestamp themselves. Source: https://raw.githubusercontent.com/radix-ui/primitives/main/packages/react/avatar/src/avatar.tsx (fetched 2026-05-05) |
events[imageError] | reshaped | onLoadingStatusChange(status='error') on Avatar.Image | Canonical `imageError` fires with `{ src, fallbackTier }`. Radix surfaces image errors as `status === 'error'` via the same `onLoadingStatusChange` callback. The `fallbackTier` payload field ('initials' | 'icon') has no Radix equivalent — Radix does not distinguish which fallback tier becomes active because it does not model the two-rung chain. Source: https://raw.githubusercontent.com/radix-ui/primitives/main/packages/react/avatar/src/avatar.tsx (fetched 2026-05-05) |
motion.durations | omitted | — | Radix Avatar ships no motion tokens or animation primitives. The library does not emit data-state attributes on Avatar.Root, Avatar.Image, or Avatar.Fallback (unlike Dialog which exposes data-state for open/closed animation). Consumers who want to animate the fallback transition write CSS directly, outside any Radix-managed surface. Source: https://raw.githubusercontent.com/radix-ui/primitives/main/packages/react/avatar/src/avatar.tsx (fetched 2026-05-05) |
motion.reducedMotionFallback | omitted | — | Radix Avatar has no reduced-motion handling. No data-state attributes are emitted, so there is no library-managed surface on which prefers-reduced-motion media queries would apply. Motion suppression is entirely outside Radix's scope for this component. Source: https://www.radix-ui.com/primitives/docs/components/avatar (fetched 2026-05-05) |
anatomy[initials] | extended | + Avatar.Fallback gains `delayMs?: number` — a Radix-specific prop absent from the canonical anatomy. When set, Fallback is withheld for the given number of milliseconds; if the image loads within that window, Fallback never renders, eliminating the flash of fallback content on fast connections. Source: https://www.radix-ui.com/primitives/docs/components/avatar#api-reference (fetched 2026-05-05) | Radix adds delayMs to solve a practical UX problem: on fast connections, images resolve before the first paint, making a briefly-visible fallback a perceptible flash of unstyled content. The canonical anatomy specifies the fallback chain order but does not model the timing of when each rung becomes visible. Radix handles this at the library layer so consumers do not need to debounce fallback rendering themselves. |
Why this audit reads the way it does
Radix Avatar is an unstyled primitive that implements the canonical image- loading and fallback-visibility contract but collapses the canonical two-rung fallback chain (initials → icon) into a single Avatar.Fallback slot. The library's primary contribution is the image-load state machine (idle/loading/loaded/error) and the delayMs flash-prevention mechanism; everything else — shape, size, status indicator, motion — is left entirely to consumers. The most significant divergence is the Fallback collapse: canonical anatomy treats initials and fallback-icon as separate slots to enforce a deterministic order and distinct accessible-name strategies. Radix treats them as one slot with any-children, which means the canonical fallback-chain contract must be re-implemented at the consumer level. The onLoadingStatusChange callback provides the mechanism; the discipline of the chain does not come from the library. Radix Avatar has no ARIA role assignments in its source — accessibility is left entirely to consumers (img alt, role="img", aria-label). This matches the library's "unstyled and accessible-by-consumer-choice" philosophy but means the canonical a11y contracts (role=img on initials/icon variants, aria-label with full entity name) are not enforced by the library.
Figma anatomy
| Slot | Figma type | Hint |
|---|---|---|
container | frame | Auto-layout fixed-size frame; aspect-ratio 1:1; border-radius bound to shape variant |
image | frame | Image fill on the container frame; aspect 1:1; object-fit cover |
initials | text | Centred text inside the container; size bound to avatar size token |
fallback-icon | instance | Icon component instance; size bound to avatar size token |
status-indicator | frame | Small circle anchored to container's bottom-right; colour bound to status enum |
Token usage per slot
container- radius
- corner
radius.full
- corner
- color
- background
color.surface.sunken - border
color.border.subtle
- background
image- color
- background
color.surface.sunken
- background
initials- color
- foreground
color.text.muted
- foreground
- typography
- size
text.sm - weight
weight.semibold
- size
fallback-icon- color
- foreground
color.text.muted
- foreground
status-indicator- radius
- corner
radius.full
- corner
- color
- border
color.surface.bg
- border
Figma ↔ Code property map
| Figma | Kind | Code | Notes |
|---|---|---|---|
Variant | Enum | shape | Maps circle / square shape variant. |
Size | Enum | size | Maps xs / sm / md / lg / xl. |
Status | Enum | status | Maps online / offline / away / busy presence indicator. |
Has Status Indicator | Boolean | hasStatusIndicator | — |
Name | Text | name | Drives initials computation and accessible-name fallback. |
Source | Text | src | Image URL; null or undefined activates initials fallback. |
Image | Slot | image | — |
Fallback Icon | Slot | fallback-icon | — |
Motion
| Transition | Duration token |
|---|---|
fadeIn | motion.duration.fast |
statusChange | motion.duration.fast |
Responsive behaviour
| Breakpoint | Change |
|---|---|
breakpoint.sm | Below this width, avatars in dense list rows collapse one size step (md → sm). Avatars in profile-header contexts keep their explicit size to preserve identity prominence. |
Internationalisation
RTL · mirroring
Status indicator anchors to the bottom-end edge via `inset-inline-end` / `inset-block-end`, so it flips from bottom-right (LTR) to bottom-left (RTL) automatically. Initials computation respects the writing direction — for LTR-name "Alex Black" the initials are "AB"; for RTL-name computation the same letter-extraction rule applies but the visual order reverses (browser-handled when initials are rendered as text content).
Text expansion
Initials do not text-expand (always 1-2 characters). Status-indicator `aria-label` text follows i18n string- expansion (German "online" → "online" stable, but "unavailable" → "nicht verfügbar" 60 % longer for SR). Avatar dimensions are size-token-driven; no width-reservation needed for text expansion.
Variants, properties, states
Variants
Structurally different versions of the component.
circle square Properties
The same component, parameterised.
| Property | Type |
|---|---|
size | xs | sm | md | lg | xl |
status | online | offline | away | busy |
hasStatusIndicator | boolean |
States
Browser/user-driven (interactive) vs. app-driven (data).
| Kind | States |
|---|---|
interactive | hoverfocus-visible |
data | image-loadingimage-loadedimage-errorinitials-fallbackicon-fallback |
Figma↔Code mismatches
- 01 Figma
Image and initials drawn as separate component variants
CodeA single Avatar component with a fallback chain (image → initials → icon)
ConsequenceDesigners ship "Avatar (image)" and "Avatar (initials)" as separate Figma components; developers ship one component with runtime fallback. The two diverge — designers may not draw the initials variant for every avatar instance, leaving developers without a design reference for the fallback case.
CorrectModel the fallback chain as a single Avatar component in both Figma and code. Document each fallback rung as a state in the canonical anatomy (image-loaded / initials-fallback / icon-fallback). Designers draw all three states for at least one representative avatar; developers ship the chain runtime.
- 02 Figma
Status indicator drawn as a separate floating element with no relationship
CodeStatus indicator anchored to the avatar with logical-property positioning
ConsequenceDesigners place a coloured dot near the avatar with absolute coordinates; developers ship `inset-inline-end` anchoring that flips under RTL. The two surfaces look the same in LTR but diverge under RTL when the status indicator stays anchored to the wrong edge.
CorrectDocument the status indicator as a slot of the Avatar component, anchored via logical properties (`inset-inline-end`, `inset-block-end`). Both Figma and code express the slot as a child of the container, not a sibling.
- 03 Figma
Initials baked into the Figma component as static text "AB"
CodeA controlled `name` prop drives the initials computation
ConsequenceDesigners ship static "AB" in every initials-variant frame; developers compute initials from a `name` prop at render time. The Figma file does not document the computation algorithm (first letter? first two? first letter of each word?) so developers reinvent it.
CorrectDocument the canonical initials algorithm in the anatomy (`first letter of each word, max two`). The Figma file uses placeholder strings for design illustration; the algorithm lives in code with the `name` prop driving it.
- 04 Figma
Avatar size hard-coded as a px value
CodeSize driven by a token (`xs / sm / md / lg / xl`)
ConsequenceDesigners set `width: 24px` directly on the avatar frame; developers bind size to a token. When the size scale rebalances, the Figma file lags and the shipped avatar visually disagrees with the surrounding text.
CorrectBind size to a token in both surfaces. Figma's size component- property maps to `xs / sm / md / lg / xl`; code uses the same enum.
Contracts
Non-negotiable contracts
Canon Fallback chain is image → initials → icon, in that order. Image rung activates when `src` is present and the load succeeds; initials rung activates when `name` is present and the image rung failed; icon rung is the ultimate fallback. Implementations that pick a different order ship inconsistent avatar surfaces under network failure or missing-data.
Without a deterministic order, the same entity may appear as initials in one surface and as the generic icon in another; the avatar's identity becomes unstable. Users lose trust in the avatar as a reliable entity-marker.
APGWAI-ARIA role=img + img alt patterns Initials and icon variants carry `role="img"` plus `aria-label` with the full entity name on the container. Image variants use `<img alt>` for the same purpose. The accessible name is always the entity name — never the initials letters, never the icon glyph name, never the file path.
Without the entity-name accessible-name, SR users hear "image" or "AB" — neither identifies the entity. The avatar becomes a sighted-only marker. Decorative avatars escape the rule by setting `aria-hidden="true"` and relying on the host's name; meaningful avatars must carry the name themselves.
Canon Status indicator carries `aria-label` with the status word ("online", "offline", "away", "busy"). The coloured dot is sighted-only; AT needs the word.
Without the label, presence is invisible to SR users — critical for collaborator-aware contexts where presence determines whether to message synchronously or asynchronously.
Vocabulary drift
- Material 3
Avatar- Polaris
Avatar- Carbon
User Avatar- Atlassian
Avatar- Radix
Avatar- Radix exposes the fallback chain as compound subcomponents (`Avatar.Image` + `Avatar.Fallback`); other libraries ship a single component with internal state. Either expression of the same canonical contract.
Common mistakes
#avatar-no-fallback-order
Fallback chain order undefined or random
The implementation falls back to initials when the image fails OR to the icon — depending on environment, network, or library version. Users see different fallback content for the same entity across surfaces; the avatar's identity becomes unstable.
Document the fallback order as image → initials → icon in the canonical anatomy. The image rung activates when the `src` is present and the load succeeds; the initials rung activates when `name` is present and the image rung failed; the icon rung activates when both prior rungs failed. Pick one and never diverge.
#avatar-no-aria-label
Initials or icon variant ships without accessible name
The avatar renders initials "AB" or a generic person icon, but the container has no `aria-label` and the host has no visible label. SR users encounter "image" with no entity-name; the avatar is meaningless to AT.
Set `role="img"` plus `aria-label` with the full entity name on the container for initials / icon variants. Image variant uses `<img alt="<entity name>">`. Decorative avatars (paired with a visible name in the layout) set `aria-hidden="true"` and let the host's name carry the accessible name.
#avatar-status-no-label
Status indicator dot has no accessible label
The status indicator renders a coloured dot anchored to the avatar's corner. SR users hear the avatar's name but no status cue; the presence-information is sighted-only.
The status indicator carries `aria-label` with the status word ("online", "offline", "away", "busy"). SR announces the status alongside the avatar's name. Document the canonical status vocabulary so every implementation announces consistently.
#avatar-initials-only-as-label
Initials are the entire accessible name
The container's `aria-label="AB"` — just the initials. SR reads "image, AB" with no entity context. Initials are visual shorthand; AT needs the full name.
`aria-label` carries the full entity name ("Alex Black") even when the visual is just the initials. The initials are visual only; the accessible name is the canonical source of truth.
#avatar-image-no-alt
Image variant has no alt attribute
The `<img>` element has no `alt` attribute (different from `alt=""`). Some SR announce the file path or URL; others silently skip the avatar entirely.
Always set `alt` on the `<img>`. `alt=""` for decorative avatars (host carries the name); `alt="<entity name>"` for meaningful avatars without a surrounding label. Never leave alt undefined.
Accessibility hints
| Slot | Accessibility hint | |
|---|---|---|
container | Container's role depends on the render-mode. Image variant wraps `<img>` carrying `alt`. Initials / icon variants set `role="img"` plus `aria-label` describing the entity (the person's name, the organisation, etc.). Decorative avatars (paired with a visible name in the surrounding layout) set `aria-hidden="true"` and let the host's name carry the accessible name. | |
image | `<img alt="">` for decorative avatars (host carries the name) or `<img alt="<entity name>">` for meaningful avatars. Never leave alt undefined — that defaults to the file path being announced in some screen readers. | |
initials | When initials carry the entity name (no surrounding label), the container carries `role="img"` plus `aria-label` with the full entity name — never just the initials, which are ambiguous to SR users (`"AB"` is meaningless without context). | |
fallback-icon | Decorative when the host carries the entity name. When the icon is the entire avatar content, the container carries `role="img"` plus `aria-label` with the entity name (or "Unknown user" / "Anonymous" placeholder if the entity is genuinely unknown). | |
status-indicator | Status indicator carries `aria-label` with the status word ("online", "offline", "away", "busy") so SR users hear it alongside the avatar's accessible name. Without the label, the coloured dot is meaningless to AT. |