Skip to content

Avatar

An identity avatar — circle / square / hexagon, with presence & status indicators, a name + detail AvatarItem, and overlapping AvatarGroups.

Stablev0.4.2added in v0.1.0@garn/ui/avatar
On this page

Default

Image with an initials fallback.

MC

Sizing

Fixed identity sizes — 16 / 24 / 32 / 40 / 48 / 96 / 128px. Intentionally not affected by density.

Shapes

circle · square · hexagon.

Fallback

Initials, or the default person glyph when empty.

Presence

online · busy · focus · offline (bottom-end, cutout ring).

Status

approved · declined · locked (top-end).

Interactive

asChild link with hover/press/focus + a presence dot.

Item

AvatarItem — name + secondary line; static and interactive/selected.

Group

AvatarGroup stack with a '+N' overflow popover.

Grid Group

AvatarGroup grid appearance + `total` (surplus tallied into '+N').

Skeleton

AvatarSkeleton loading placeholders (circle / square / hexagon).

Installation

garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.

terminal
npx garn-ui add avatar
First time? Set up garn in your project
Registry
avatar
Deps
@radix-ui/react-avatar@radix-ui/react-slotclass-variance-authoritylucide-react
Registry deps
utilspopoverscroll-area

Import

import { Avatar, AvatarImage, AvatarFallback, AvatarSkeleton, AvatarPresence, AvatarStatus, AvatarItem, AvatarGroup } from "@garn/ui/avatar";

Anatomy

The parts this component is built from — compose them to assemble it.

AvatarRequired[data-slot="avatar"]

The frame; carries the size + shape variants. Not overflow-hidden, so indicators can sit past the edge. asChild makes it an interactive <a>/<button>.

AvatarImage[data-slot="avatar-image"]

The photo; needs alt. Rounds/clips itself to the frame shape. Hidden until it loads, then revealed over the fallback.

AvatarFallbackRequired[data-slot="avatar-fallback"]

Initials shown while the image loads or if it fails. With no children it renders a neutral person glyph — never a blank disc.

AvatarSkeleton[data-slot="avatar-skeleton"]

A pulsing loading placeholder matching the avatar's size + shape (hexagon self-clips). Reduced-motion-safe.

AvatarPresence[data-slot="avatar-presence"]

A presence dot anchored bottom-end (online / busy / focus / offline) with a surface-color cutout ring and an sr-only state label.

AvatarStatus[data-slot="avatar-status"]

A status glyph anchored top-end (approved / declined / locked) with a surface-color cutout ring and an sr-only state label.

AvatarItem[data-slot="avatar-item"]

An avatar + primaryText (+ optional secondaryText) row for lists and menus. asChild promotes the whole row to a button/link; supports selected / disabled.

AvatarGroup[data-slot="avatar-group"]

A labelled list of avatars — stack (overlapping, surface-ring separated) or grid (even gutters) — capped at max, with a '+N' overflow chip.

Props

The component's public props and their types.

shape
Description
No description.
Type
"circle" | "hexagon" | "square"
Default
circle
size
Description
No description.
Type
"2xl" | "2xs" | "lg" | "md" | "sm" | "xl" | "xs"
Default
md
asChild
Description
Render the consumer's element (an `<a>`/`<button>`) as the root — makes the avatar interactive (hover tint + focus ring).
Type
boolean
Default
false

Plus 280 inherited native <span> attributes.

AvatarSkeleton

shape
Description
Match the avatar it stands in for.
Type
AvatarShape
Default
"circle"
size
Description
Match the avatar it stands in for.
Type
AvatarSize
Default
"md"

Plus 280 inherited native HTML attributes.

AvatarPresence

label
Description
Accessible label; defaults to the capitalized state word.
Type
string
typeRequired
Description
Which presence state to show.
Type
AvatarPresenceType

Plus 279 inherited native HTML attributes.

AvatarStatus

label
Description
Accessible label; defaults to the capitalized state word.
Type
string
typeRequired
Description
Which status to show.
Type
AvatarStatusType

Plus 279 inherited native HTML attributes.

AvatarItem

asChild
Description
Promote the whole row to the consumer's interactive element (a `<button>`/`<a>`).
Type
boolean
Default
false
avatar
Description
The leading Avatar.
Type
React.ReactNode
primaryTextRequired
Description
The primary line — usually the person's name.
Type
React.ReactNode
secondaryText
Description
An optional muted second line under the primary.
Type
React.ReactNode
selected
Description
Reflected as `data-selected` and styled as the active row.
Type
boolean

Plus 280 inherited native HTML attributes.

AvatarGroup

appearance
Description
`stack` overlaps the avatars; `grid` lays them out with even gutters.
Type
"grid" | "stack"
Default
"stack"
label
Description
Accessible name for the group (required — the group is a labelled list).
Type
string
max
Description
Show at most this many avatars before collapsing the rest into "+N". Defaults to 5 (stack) / 11 (grid).
Type
number
moreLabel
Description
Accessible label for the "+N" chip (default: "N more").
Type
string
onAvatarClick
Description
Fires with the avatar's index when a stacked avatar is clicked.
Type
(index: number) => void
onMoreClick
Description
Fires when the "+N" chip is clicked. When set, it replaces the default popover.
Type
() => void
shape
Description
Shape applied to every avatar + the chip.
Type
AvatarShape
Default
"circle"
size
Description
Size applied to every avatar + the chip.
Type
AvatarSize
Default
"sm"
total
Description
Total people represented when not all are passed as children — the surplus is added to "+N".
Type
number

Plus 279 inherited native HTML attributes.

Styling

Target these data-slots and states, and remap these tokens, to restyle without forking the component.

[data-slot="avatar"]

States

SelectorState
[data-slot=avatar-image]image-loaded
[data-slot=avatar-fallback]fallback-shown
[data-interactive]interactive
[data-selected]selected
PropertyTokenTier
presence-online--garn-avatar-onlinebrandablecomponent
presence-busy--garn-avatar-busybrandablecomponent
presence-focus--garn-avatar-focusbrandablecomponent
presence-offline--garn-avatar-offlinebrandablecomponent
status-locked--garn-avatar-lockedbrandablecomponent

Private instance vars (never externalize): --avatar-hex

When to use

Represent a person or entity with their photo, falling back to initials or a person glyph — with optional presence/status, a name + detail AvatarItem, or an AvatarGroup that stacks many.

Reach for something else when

  • A decorative or content image (use img / aspect-ratio)
  • a standalone status dot or unread count (use a badge / counter)
  • a large hero image.

Overview

Avatar represents a person or entity with their photo, gracefully falling back to initials or a glyph when the image is missing or still loading. AvatarImage + AvatarFallback are the core; AvatarStatus/AvatarPresence add a status dot, AvatarSkeleton holds the shape while loading, and an AvatarGroup stacks several with a "+N" overflow. It's an identity marker — not a button — so pair it with a name and wrap it in a control when it should be clickable.

Guidelines

Always provide a real fallback. Photos fail, lag, or simply don't exist; give every avatar initials (or a glyph) so it never renders empty. Derive initials from the person's name and keep them to one or two characters.

Give it an accessible name, not just a picture. Set meaningful alt/label text (the person's name) — a decorative-only avatar leaves screen-reader users with nothing. In a group, summarize the overflow ("+5 more") rather than hiding who's there.

Use the status dot for presence, sparingly. A green/idle/offline dot communicates availability at a glance, but don't overload the corner with multiple indicators. Keep sizes on the avatar's own scale and pair small avatars with a name when identity matters.

Best practices

Do
  • Always include an AvatarFallback (initials or the default glyph) for the loading/missing-image case.
  • Give AvatarImage a meaningful alt; give an interactive (asChild) avatar an aria-label.
  • Use the fixed size scale — avatars are identity geometry, not density-driven.
  • Use AvatarPresence (bottom-end) for presence and AvatarStatus (top-end) for status; give every AvatarGroup a label.
Don't
  • Don't ship an Avatar with no fallback.
  • Don't wrap the avatar in an overflow-hidden box if it carries presence/status — that clips the indicator.
  • Don't shrink avatars with density; the size scale is intentional.

Content guidelines

  • Use 1–2 uppercase initials (first + last) for the fallback; keep fallbacks consistent across a list.
  • Write an alt that names the person or entity — not “avatar” or “user image”.
  • When an AvatarGroup represents more people than you render, pass total so '+N' reflects the real count.

Troubleshooting

The avatar is blank when the image URL is missing or slow.

Cause. No AvatarFallback was provided.

Fix. Always render an AvatarFallback (initials, or leave it empty for the default person glyph) as a child.

A screen reader doesn't announce a clickable avatar.

Cause. An interactive (asChild) avatar has no accessible name on its <a>/<button>.

Fix. Add aria-label (the person's name) to the link/button, or give AvatarImage a meaningful alt.

The presence/status dot is clipped or cut off.

Cause. The avatar (or a wrapping container) was given overflow-hidden — the indicator sits past the frame edge.

Fix. Don't add overflow-hidden to the Avatar or its wrapper; the image/fallback already clip themselves.

AvatarGroup shows fewer avatars than expected, or the '+N' count looks off.

Cause. max reserves the last slot for the '+N' chip; the chip counts everything beyond the shown avatars.

Fix. Raise max, or pass total when the group represents more people than the children you render.

Editing dark-mode presence/status colors in the component has no effect.

Cause. They resolve through the --garn-avatar-* component tokens; the dark values live in the token layer.

Fix. Adjust dark mode in the garn theme CSS (.dark block) — never with raw ramps in avatar.tsx.

Accessibility

Accessibility requirements

warn

Give the interactive avatar an accessible name — aria-label (the person's name) on the <a>/<button>, or a meaningful AvatarImage alt / AvatarFallback text.

when an Avatar is interactive (asChild) but the link/button, the AvatarImage alt, and the AvatarFallback text are all empty

warn

Give the AvatarGroup an accessible name via `label` (e.g. "Project team").

when an AvatarGroup has no `label` or `aria-label`

A1.1.1Non-text ContentA1.4.1Use of ColorA4.1.2Name, Role, Value
  • AvatarImage is an <img> — give it a meaningful `alt` (the person/entity name), or alt="" if it is purely decorative beside a text label.
  • AvatarFallback (initials or the default person glyph) is what assistive tech reads before/if the image loads.
  • An interactive avatar (asChild) needs its own accessible name — put aria-label (the person's name) on the <a>/<button>; a meaningful AvatarImage alt or AvatarFallback text also counts. A dev-time warning fires when none is found.
  • AvatarPresence / AvatarStatus convey their state in text via an sr-only label (e.g. 'Online', 'Approved') and a distinct glyph shape — never by color alone.
  • AvatarGroup is a labelled list (role=list) — give it an accessible name via `label`; a dev-time warning fires when missing. The '+N' chip is a button labelled 'N more'.