Avatar
An identity avatar — circle / square / hexagon, with presence & status indicators, a name + detail AvatarItem, and overlapping AvatarGroups.
@garn/ui/avatarDefault
Image with an initials fallback.
Sizing
Fixed identity sizes — 16 / 24 / 32 / 40 / 48 / 96 / 128px. Intentionally not affected by density.
2xsxssmmdlgxl2xlShapes
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.
npx garn-ui add avatar- Registry
avatar- Source
- avatar.tsx
- Deps
@radix-ui/react-avatar@radix-ui/react-slotclass-variance-authoritylucide-react- Registry deps
utilspopoverscroll-area
Import
import { Avatar } from "@garn/ui/avatar";Props
Generated from the component's TypeScript 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 — interactive.
- Type
boolean
Plus 280 inherited native <span> attributes.
AvatarSkeleton
shape- Description
- No description.
- Type
AvatarShape
size- Description
- No description.
- Type
AvatarSize
Plus 280 inherited native HTML attributes.
AvatarPresence
label- Description
- Accessible label; defaults to the capitalized state word.
- Type
string
typeRequired- Description
- No description.
- Type
AvatarPresenceType
Plus 279 inherited native HTML attributes.
AvatarStatus
label- Description
- Accessible label; defaults to the capitalized state word.
- Type
string
typeRequired- Description
- No description.
- Type
AvatarStatusType
Plus 279 inherited native HTML attributes.
AvatarItem
asChild- Description
- Promote the row to the consumer's interactive element (a `<button>`/`<a>`).
- Type
boolean
avatar- Description
- The leading Avatar.
- Type
React.ReactNode
primaryTextRequired- Description
- No description.
- Type
React.ReactNode
secondaryText- Description
- No description.
- 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. Default `stack`.
- Type
"grid" | "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". Default 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
size- Description
- Size applied to every avatar + the chip.
- Type
AvatarSize
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 remap these tokens to restyle without forking the component.
[data-slot="avatar"]| Property | Token | Tier |
|---|---|---|
presence-online | --garn-avatar-onlinebrandable | component |
presence-busy | --garn-avatar-busybrandable | component |
presence-focus | --garn-avatar-focusbrandable | component |
presence-offline | --garn-avatar-offlinebrandable | component |
status-locked | --garn-avatar-lockedbrandable | component |
Private instance vars (never externalize): --avatar-hex
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
- 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 anaria-label. - Use the fixed
sizescale — 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 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
totalso '+N' reflects the real count.
Accessibility
Enforceable contracts
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
Give the AvatarGroup an accessible name via `label` (e.g. "Project team").
when an AvatarGroup has no `label` or `aria-label`
- • 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'.
- Status
- Stable
- Version
v0.3.0- Introduced
v0.1.0
garn doesn't yet record a per-release version history. This reflects the component's current lifecycle state; full changelog entries will appear once the metadata pipeline emits a lifecycle.history field.