DocumentationComponentsTheme CreatorGitHub
Theme CreatorGitHubIntroduction
InstallationUsageTheming
ComponentsAccordionAction SheetAlertAlertDialogArea ChartAspectRatioAvatarBadgeBannerBar ChartBottom NavBreadcrumbButtonButtonGroupCalendarCardCarouselChartChatBubbleChatBubbleNewCheckboxChipCoachMarkCodeBlockCollapsibleColor PickerComboboxCommandContainerContextMenuDate PickerDividerDrawerDropdown MenuEmptyStateFABFieldFieldsetFile UploadFlexFormGridHoverCardIconButtonLabelLine ChartLinkMenubarMeterModalNavigation MenuNumberFieldOTP InputPaginationPickerPie ChartPopoverProgressPromptInputRadar ChartRadial ChartRadioRatingScroll AreaSearchSegmentedControlSelectShortcutSidebarSkeletonSliderCircularSliderMediaTrimmerSpacerSpinnerSplit ButtonStackStatusStepperSurfaceSwitchTableTabsTextareaTime PickerToastToggleButtonToggleGroupTokenizerToolbarTooltipTop Header DesktopTop Header Mobile
Contributing
Components

Avatar

A picture of a person or thing with an automatic fallback chain — image, second image, initials, icon — an optional status dot composed into its accessible name, interactive hosts, a stacking group and a skeleton.

Playground

Installation

pnpm add @tessinaui/ui

Usage

import { Avatar, AvatarGroup, AvatarSkeleton } from "@tessinaui/ui";
<Avatar src="/avatars/jane.jpg" name="Jane Doe" presence="online" />
<Avatar name="Jane Doe" />                      {/* "JD" on the neutral plate, named "Jane Doe" */}
<Avatar name="Jane Doe" render={<button />} />  {/* a control named "Jane Doe" */}

<AvatarGroup aria-label="Collaborators" max={3}>
  <Avatar src="/avatars/jane.jpg" name="Jane Doe" />
  <Avatar src="/avatars/john.jpg" name="John Roe" />
  <Avatar src="/avatars/ada.jpg" name="Ada Lovelace" />
  <Avatar src="/avatars/grace.jpg" name="Grace Hopper" />
</AvatarGroup>

Examples

Default

An image named by name. The <img> inside is decorative; the avatar itself is the image, announced once as "Jane Doe, image".

Fallback chain

src → fallbackSrc → initials from name → icon. A failed URL is remembered, so a changed src gets a fresh attempt. With no image and no name there is nothing to announce, so the last avatar is decorative.

Initials from name

First character of the first word plus first character of the last word, uppercased — middle names are skipped, a single word gives one letter, and the first user-perceived character is used, so a name that starts with an emoji or a combining sequence does not render half a glyph. An explicit initials wins and is capped at two characters.

Loading status

onLoadingStatusChange reports idle → loading → loaded or error. delay holds the fallback back for that many milliseconds, so a fast image never flashes its initials first.

Sizes

Seven sizes — 20 / 24 / 32 / 40 / 56 / 64 / 96 px. The list-row tier (md, lg) is where most avatars live; 3xl is the hero. Initials are a fixed fraction of the diameter (see Sizes below).

Rounded

full is the default. The image and the initials clip to the corners, and a group ring or focus ring takes the same radius.

With presence

presence — where someone is. The dot is sized and ringed for the avatar and shape-coded as well as coloured (disc, moon, bar, hollow ring), so the four states read apart without colour. Its label joins the name: "Jane Doe, Online".

With status intent

statusIntent — what state something is in. The intent's icon shows inside the dot from 12 px up (avatars lg and larger); statusShowIcon={false} keeps a plain dot. statusLabel replaces the default English word in the accessible name — "Jane Doe, Sync failed" says more than "Jane Doe, Error".

Status position

Four logical corners; bottom-end is the default. On a circle the dot is centred on the 45° point of the edge, because the bounding-box corner lies outside the circumference — placed with calc() on logical insets, so it mirrors under dir="rtl" with no transform involved; on any other rounded it sits in the corner. The dot carries data-position for styling hooks.

Status pulse

statusPulse animates the dot, off under prefers-reduced-motion. Reserve it for a state that needs attention now.

Ring

ring draws a full circle around the picture — a tier, a highlight, an unseen story. ringValue (0–100) draws it as an arc over a track instead, for completeness. ringIntent colours it; none is the quiet neutral a seen story or a base tier wants. The ring lives outside the picture — a ringed face is exactly as big as a bare one and the footprint grows by ringOutsetPx(size) on each side, so a list of mixed avatars keeps one face size.

Ring as progress

The onboarding pattern: an arc for the number, a pill under it for the words. The ring stays decorative here — the visible pill already says "10 % complete", and ringLabel would have it announced twice. Give ringLabel only when the ring is the sole carrier; it then becomes a progressbar with aria-valuenow.

Story ring

ringVariant="gradient" paints the ring from --avatar-ring-from to --avatar-ring-to. Both default to our tokens (primary → info); a product sets the two variables to its own brand, which is the one place a colour of yours belongs. A seen story drops to ringIntent="none".

Tier ring

One ring colour per level — with the level also written down, because colour is never the only carrier of meaning (WCAG 1.4.1).

Badge

badge pins a pill to the picture's edge, centred — "NEW", a tier, a percentage. It is a slot: pass a Badge or a Chip and it keeps its own semantics. badgePosition="top" moves it to the top edge.

Colours

color paints the plate behind initials or the icon. It is the intent palette under a grandfathered name; an image covers the plate, so it has no effect there.

With name

showName renders name as text beside the picture, with one description line under it. The text then is the accessible name — the picture goes decorative and a status label joins as hidden text — so "Jane Doe, Online" is announced once, not as an image and then a caption. The type scale follows the avatar size; xs has no room for a description and drops it. Both lines truncate to one line by default (truncate={false} to wrap); nameMaxWidth caps the text block.

Name position

Four logical positions (namePosition): end — after the picture — is the default and what lists, rows and chat use (~65 % of product screens surveyed); bottom is the profile-page form and centres by default (~28 %); start puts the text first for trailing-avatar rows (~5 %); top exists for completeness (~2 %). align overrides the default alignment. All four mirror under dir="rtl".

Interactive with name

With a visible name the host wraps the whole row — picture and text are one hit target — and takes its accessible name from the text. Add your own padding through className.

Corner action

action + actionLabel put a small primary icon button on the picture's edge — the upload or edit affordance on every profile screen. It is a real button with a required name, a ring in the backdrop colour and a 44 px hit box on phones. It defaults to bottom-end (actionPosition moves it); a status dot asked for the same corner moves to the opposite vertical corner on its own. Below md the badge would cover the face, so it warns in development. With render, the host wraps the picture only — two controls cannot nest — and is labelled by the visible name.

Photo upload

onFileSelect turns the action into a real file picker: a hidden <input type="file"> behind the badge, so it is focusable, named by actionLabel and operable from the keyboard — not a button that pretends. It accepts image/* unless actionAccept says otherwise. The file is yours to handle; nothing is uploaded for you.

Interactive

render puts the avatar inside a control. The host carries the composed name — "Jane Doe, Online, button" — and the avatar inside it is presentation, so a screen reader hears one control, not an image inside a button. An anchor host keeps link semantics, and onClick alone implies a <button> host — the contract AvatarGroupCount and Chip keep. An interactive avatar with no name warns in development: "Online" is not a name for a control.

On a coloured surface

Nothing here sets tone. Inside a <Surface> every avatar inherits tone="on-color": the neutral plate becomes a tint of the surface's ink, and the status dot's cut-out and the group's backdrop rings are painted in the surface's own fill (--surface-fill), not the page background.

Group

A stack. Overlap is 25 % of the size, each avatar is separated by a ring in the backdrop colour, and past max the rest collapse into "+N" with a real count in its accessible name ("+2 more"; 100 or more shows "99+"). size and rounded cascade to every child; a child's own value wins. The group must be named.

Overlap

overlap="auto" (the default) tucks each avatar 25 % of its size under the previous one, so the cluster keeps the same proportions at every size. Pass a number to set the overlap in px instead — lower is airier, higher packs tighter for dense, space-constrained UI.

Stacking order

By default the first member sits on top of the stack, so the first credited author stays the most prominent. reverse puts the last member on top. The visual left-to-right order does not change.

Group count and actions

<AvatarGroupCount> is an explicit last item: a count you manage yourself (+12, named by aria-label), or an icon with a render host, which makes it a real button — "Add a member". It takes the group's size, corners and ring and is never part of max's tally.

Group overflow button

onOverflowClick turns the "+N" item into a button — to open the member list — named by overflowLabel.

Group with interactive avatars

When the members are controls the group is one Tab stop with arrow-key movement (roving tabindex), so four assignees are one stop, not four.

Skeleton

AvatarSkeleton reads its diameter and radius from the same scale as the real avatar and announces "Loading" as a live region.

RTL

Positions are logical, so bottom-end mirrors to the bottom-left in RTL on its own. dir on the avatar sets its direction; inside a dir="rtl" container it is inherited. statusLabel localises the status's share of the name.

Identity colour

identity gives an initials avatar a deterministic plate so a roster stops rendering identical grey circles. Pass a stable id, never a display name — names change, and the person's colour would change with them:

<Avatar name="Jane Doe" identity={user.id} />
  • A string is hashed (FNV-1a, modulo 8) onto --identity-1…8. The hash is a stability contract: the same string maps to the same hue forever.
  • A number is a direct palette index (0–7) for a server-assigned colour — Slack-style systems store the colour as data; pass it straight through.
  • An explicit color always wins over identity.

The colour is decoration, not identity (WCAG 1.4.1): with 8 hues a 12-person roster is guaranteed collisions, and that is fine — the initials and the accessible name carry who the person is. Each hue's initials-on-plate pair is hard-gated at WCAG AA in both themes. The trade accepted with it: identity plates sit closer to the page colour than --secondary (≈1.05–1.16:1 light), so stacked AvatarGroup silhouettes rely on the ring rather than the plate edge.

API Reference

Avatar

Extends the native attributes of the element it renders into (a <span>, or the render host) except color.

PropTypeDefaultDescription
srcstring—Image source
fallbackSrcstring—A second image to try when src fails, before falling back to initials
namestring—The person's (or thing's) name. Drives the accessible name, the initials fallback and the image alt. Without it the avatar is decorative — role="presentation", hidden from assistive technology
alt (deprecated)string—Alias for name
initialsstringderived from nameOverride the derived initials; at most two characters are shown, uppercased
iconReactNodea person silhouetteFallback icon when there is neither image nor initials
delaynumber0Milliseconds to wait before showing the fallback, to avoid a flash on fast loads
onLoadingStatusChange(status: "idle" | "loading" | "loaded" | "error") => void—Fires as the image moves through idle → loading → loaded or error. Only while an image is in play — an initials-only avatar reports nothing. Removing a loaded src brings the fallback back
size"xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl""lg"Diameter: 20 / 24 / 32 / 40 / 56 / 64 / 96 px
rounded"none" | "sm" | "md" | "lg" | "xl" | "full""full"Corner treatment
color"default" | "primary" | "error" | "warning" | "success" | "info" | null"default"Plate colour behind initials or the icon — the intent palette under a grandfathered name. Ignored behind an image; null paints no plate
tone"default" | "on-color"inherited from <Surface>On-color, the neutral plate becomes a tint of the ink
showNamebooleanfalseRender name as visible text beside the picture; the text then IS the accessible name and the picture goes decorative
namePosition"end" | "start" | "bottom" | "top""end"Where the text sits — logical, so it mirrors in RTL
descriptionReactNode—One secondary line under the name (a role, an email). Visible text only, never part of the label; dropped at xs
align"start" | "center""start" beside, "center" above/belowText alignment
truncatebooleantrueOne line each for name and description, with an ellipsis; the full name stays in title
nameMaxWidthnumber | string—Width cap for the text block
ringbooleanfalseDraw a full ring around the picture — a tier, a highlight, an unseen story
ringValuenumber—0–100: draws the ring as an arc over a track instead. Implies ring; out-of-range values are clamped and warn
ringIntent"primary" | "error" | "warning" | "success" | "info" | "none""primary"Ring colour. none is the neutral boundary — a seen story, a base tier
ringVariant"solid" | "gradient""solid"gradient paints from --avatar-ring-from to --avatar-ring-to (primary → info by default)
ringLabelstring—What the ring means. Given, it is announced — a progressbar with aria-valuenow when ringValue is set, an img otherwise. Omitted, the ring is decorative
badgeReactNode—A pill pinned to the picture's edge, centred — a Badge or a Chip
badgePosition"bottom" | "top""bottom"Which edge the badge sits on
presence"online" | "away" | "busy" | "offline"—Presence dot in the corner, sized and ringed for this avatar
statusIntent"none" | "error" | "warning" | "success" | "info"—Intent dot in the corner. Ignored when presence is set
status (deprecated)StatusVariant—The old single enum; mapped onto presence / statusIntent
statusLabelstringthe English word for the stateThe status's contribution to the accessible name — "Jane Doe, Online". A neutral dot (statusIntent="none") says nothing unless you give it words here
statusPositionAvatarStatusPosition"bottom-end"Corner the dot sits in
statusShowIconbooleantrue for intentsShow the intent icon inside the dot where it fits (12 px dots and up)
statusPulsebooleanfalsePulse the status dot
actionReactNode—An icon for a small primary button on the picture's edge — upload, edit, add. Requires actionLabel (TypeScript insists)
actionLabelstring—Accessible name of the action button
onActionClickMouseEventHandler—Click handler of the action button
onFileSelect(file: File | null, event) => void—Turns the action into a real file picker — a hidden <input type="file"> behind the badge. You own the file
actionAcceptstring"image/*"What the picker accepts
actionPositionAvatarStatusPosition"bottom-end"The action's corner. A status dot in the same corner moves to the opposite vertical one
renderReactElement—Render into an interactive host — <button> or <a> — to make the avatar a control. The host gets the accessible name, onClick, the focus ring and the avatar's corners. With an action it wraps the picture only, and Avatar's ref, className and other props stay on the outer element — reach the host through a ref on the render element
onClickMouseEventHandler—Makes the avatar a button when no render is given — the host is implied
dir"ltr" | "rtl"—Text direction; decides which side end is
showIconFallbackbooleantrueShow the icon fallback when there is no image and no initials

Data attributes on the picture: data-slot="avatar", data-size, data-rounded, data-tone; the dot is data-slot="avatar-status" with data-position; the action button data-slot="avatar-action"; the ring data-slot="avatar-ring" with data-intent and data-value; the edge pill data-slot="avatar-badge" with data-position; with a visible name the row is data-slot="avatar-item" with data-name-position, the text block avatar-text / avatar-name / avatar-description; an interactive host is data-slot="avatar-host" with data-interactive.

AvatarGroup

Extends the native <div> attributes. One of aria-label / aria-labelledby is required — TypeScript enforces it.

PropTypeDefaultDescription
aria-label / aria-labelledbystringrequired (one of)The group's name
maxnumberall shownAvatars shown before the rest collapse into a "+N" overflow
sizeAvatarSize"lg"Applied to every child; a child's own size wins
roundedAvatarRounded"full"Applied to every child; a child's own rounded wins
overlap"auto" | number"auto"How far each avatar tucks under the previous one: auto is 25 % of the size; a number is px
reversebooleanfalsePut the last member on top of the stack instead of the first; the left-to-right order is unchanged
overflowLabel(count: number) => string(n) => `${n} more`The overflow item's accessible name, in full — the visible "+N" is hidden from assistive technology, so Show 3 more reads exactly that
onOverflowClickMouseEventHandler<HTMLButtonElement>—Makes the "+N" item a button — to open the member list
dir"ltr" | "rtl"—Stacking direction

The overlap is published as --avatar-overlap (in px) on the group, and each member is ringed in --surface-fill (the page background outside a coloured Surface). Data attributes: data-slot="avatar-group", data-size, data-reverse; the automatic overflow item is data-slot="avatar-overflow", an explicit one data-slot="avatar-count".

AvatarGroupCount

The explicit last item of a group — a count or an icon action. Takes the group's size, rounded, tone and ring by default; never part of max's tally.

PropTypeDefaultDescription
childrenReactNode—The count text (+12) or an icon. An icon alone needs aria-label (warns in development)
aria-labelstring—Accessible name. On a static count it is rendered as hidden text ("12 more"); on a button it is the button's name
renderReactElement—A host — <button /> or <a href /> — that makes the item a control
onClickMouseEventHandler—Makes the item a button when no render is given
size / rounded / toneas Avatarfrom the groupOverrides

AvatarSkeleton

Extends the native <div> attributes.

PropTypeDefaultDescription
sizeAvatarSize"lg"Same scale as the avatar
roundedAvatarRounded"full"Same corners as the avatar
showNamebooleanfalseReserve space for a name (and description) beside the picture
namePositionAvatarNamePosition"end"Where the reserved text sits
ringbooleanfalseReserve the ring's outset, so nothing shifts when the real avatar arrives

AvatarStatusPosition

type AvatarStatusPosition = "bottom-end" | "bottom-start" | "top-end" | "top-start";

Logical corners: end is the right side in LTR and the left in RTL. The physical names are deprecated (AvatarStatusPositionLegacy) and mapped:

DeprecatedMaps to
"bottom-right""bottom-end"
"bottom-left""bottom-start"
"top-right""top-end"
"top-left""top-start"

Because the mapping is logical, a legacy "top-right" now renders at the top left under dir="rtl".

Exports

AVATAR_SIZE (per-size container, text, icon, status-dot tier, overlap, corner offset and ring metrics), ringOutsetPx(size) (what a ring adds on each side), AVATAR_ROUNDED, avatarVariants (the color plate classes), and AVATAR_STATUS_SIZE / AVATAR_STATUS_RING (the Status dot's own scale, re-exported). Types: AvatarProps, AvatarActionProps, AvatarGroupProps, AvatarGroupCountProps, AvatarSkeletonProps, AvatarSize, AvatarRounded, AvatarTone, AvatarStatusPosition, AvatarStatusPositionLegacy, AvatarNamePosition, AvatarRingIntent, AvatarRingVariant.

Sizes

SizeDiameterInitialsIconStatus dotGroup overlap
xs20 px8 px10 px2xs (6 px)5 px
sm24 px10 px12 pxxs (8 px)6 px
md32 px12 px16 pxsm (10 px)8 px
lg40 px14 px20 pxmd (12 px)10 px
xl56 px20 px28 pxlg (16 px)14 px
2xl64 px24 px32 pxlg (16 px)16 px
3xl96 px36 px48 pxxl (20 px)24 px

The dot is 25–30 % of the diameter on list sizes and ~20 % on the heroes, and its cut-out ring steps with it.

Migrating from the previous API

  • alt → name. Same string; it now also drives the initials and the accessible name.
  • status="online" → presence="online"; status="error" → statusIntent="error"; status="neutral" → statusIntent="none".
  • statusPosition physical names → logical names (table above). The default moved from top-right to bottom-end.
  • AvatarGroup now requires aria-label / aria-labelledby, and max no longer defaults to 5 — omit it to show everyone.
  • An avatar without name (or alt) is now decorative — hidden from assistive technology — rather than an image with no name.

Accessibility

There is no APG pattern for an avatar; it is an image, or a control that looks like one. The accessible name is the whole subject.

Naming rules

  • Named, not interactive — role="img" with aria-label = name. The image inside has alt="", the initials and the icon are aria-hidden, so the name is read exactly once.
  • Named, with a status — the status label is composed into the name: aria-label="Jane Doe, Online". The dot inside is a decorative Status (label={null}); one announcement, not an image followed by a second image called "Online".
  • Unnamed — role="presentation" and aria-hidden="true". An avatar with nothing to say is decorative, which is better than a meaningless "image".
  • Interactive (render, or onClick alone) — the host carries the composed name (<button aria-label="Jane Doe, Online">) and the avatar inside is presentation. An aria-label on the render element itself wins. Without a name the control has no name, and development warns.
  • Visible name (showName) — the text is the name (Primer's and Atlassian's rule): the picture becomes aria-hidden, the status label is appended as visually hidden text ("Jane Doe, Online"), and description is visible text only — never part of the label. An interactive host wraps the row and is named by its text; with an action it wraps the picture only and is aria-labelledby the visible name (and aria-describedby the description).
  • Corner action — a real <button> named by actionLabel (required by type), separate from the avatar's own name: "Change photo, button". With onFileSelect it is a real <input type="file"> instead, named the same way, so the picker opens from the keyboard.
  • Ring — decorative by default. ringLabel promotes it to a progressbar (with aria-valuenow) or an img. Do not set it when a visible pill already says the same thing, or it is read twice.
  • Badge — a slot, so whatever you pass keeps its own semantics; the pill's text is read as part of the surrounding content, not folded into the name.

What is announced (VoiceOver)

MarkupAnnouncement
<Avatar name="Jane Doe" />"Jane Doe, image"
<Avatar name="Jane Doe" presence="online" />"Jane Doe, Online, image"
<Avatar name="Jane Doe" presence="away" statusLabel="Abwesend" />"Jane Doe, Abwesend, image"
<Avatar initials="JD" />nothing — decorative
<Avatar name="Jane Doe" presence="online" render={<button />} />"Jane Doe, Online, button"
<Avatar name="Jane Doe" render={<a href="/jane" />} />"Jane Doe, link"
<Avatar name="Jane Doe" showName description="Design lead" presence="online" />"Jane Doe, Online" as text, then "Design lead" — no image announced
<Avatar name="Jane Doe" showName render={<button />} />"Jane Doe, button" (the row is the control)
<Avatar name="Jane Doe" action={<Camera />} actionLabel="Change photo" />"Jane Doe, image" then "Change photo, button"
<AvatarGroupCount render={<button />} aria-label="Add a member"><Plus /></AvatarGroupCount>"Add a member, button"
<Avatar name="Jane Doe" ringValue={10} />"Jane Doe, image" — the ring is decorative
<Avatar name="Jane Doe" ringValue={10} ringLabel="Profile 10% complete" />"Jane Doe, image" then "Profile 10% complete, 10 %, progress indicator"
<Avatar … action={<Camera />} actionLabel="Change photo" onFileSelect={…} />"Change photo, file upload button"
<AvatarGroup aria-label="Collaborators" max={3}>"Collaborators, group", then each member, then "+2 more"
overflow with onOverflowClick"3 more, button" (or your overflowLabel)
<AvatarSkeleton />"Loading" (role="status", aria-busy)

Why the parts are hidden

  • Initials are aria-hidden because "JD" is not a name — it would be read as two letters, and the name is already on the root.
  • The dot is aria-hidden because its meaning is already in the name. A second role="img" called "Online" would announce the state twice and, on a group, double the number of stops.
  • The image has alt="" for the same reason: the root is the image.

Keyboard — AvatarGroup with interactive members

KeyResult
TabMoves focus into the group — one stop, on the last-focused member
→ / ←Next / previous member; direction-aware in RTL; wraps at the ends
Home / EndFirst / last member
Enter / SpaceActivates the member (an anchor host takes Enter only, as links do)

The overflow button and an interactive AvatarGroupCount, when present, are part of the same roving set. A group of non-interactive avatars has no Tab stop at all.

Visual

  • Presence is shape-coded as well as coloured (disc, moon, bar, hollow ring — the library's own icons), so it does not rely on colour alone (WCAG 1.4.1).
  • The ring is drawn outside the picture, so a ringed avatar keeps the same face size as a bare one; the footprint grows instead. A tier or a story is never colour alone — pair it with a badge, a caption or ringLabel.
  • The corner action badge is small on purpose (≈45 % of the diameter, 20 px floor) but its hit box is 44 px on phones through an invisible tap extension; the ring in the backdrop colour cuts it out of the picture.
  • The status dot's cut-out ring and the group's rings take the colour of the backdrop — --surface-fill inside a Surface, the page otherwise — so they survive a coloured or dark surface.
  • The focus ring on an interactive host takes the avatar's corners, so it hugs a circle rather than boxing it.
  • An avatar below 44 px is not a touch target on its own. Give a tappable one a host with enough padding, or put it inside a row that is the target.
  • The press scale and the pulse are disabled under prefers-reduced-motion.

When to use which

  • Avatar — a picture of who or what. It can carry presence or an intent dot, but the dot is about the avatar's subject.
  • Status — the dot on its own, next to a name in a list, or as a standalone "Online" / "Sync failed" indicator with its own accessible name and an optional visible label.
  • Badge — a count or a label overlaid on something else (an icon, a tab). Not presence.
  • Chip — a person as a removable token or a filter. The chip's label names the person and its built-in avatar is decoration.

Name it when it stands alone; hide it beside the same visible name. A lone avatar — in a toolbar, a reaction strip, a "who's viewing" row — is the only thing that says who this is, so it needs name. Beside a visible "Jane Doe" (a list row, a message header) the same name would be read twice; keep name for the initials and add aria-hidden="true", which is what Chip does with its own avatar. If the row shows presence only as the dot, keep the avatar named — a repeated name is better than a missing state — or show the presence as text with <Status presence="online" showLabel />.

Non-goals. An avatar is not a button by default; reach for render when it opens something, and then make sure it has a name. It does not upload or crop a photo, and it does not stack itself — that is AvatarGroup.

AspectRatioBadge

On this page

PlaygroundInstallationUsageExamplesDefaultFallback chainInitials from nameLoading statusSizesRoundedWith presenceWith status intentStatus positionStatus pulseRingRing as progressStory ringTier ringBadgeColoursWith nameName positionInteractive with nameCorner actionPhoto uploadInteractiveOn a coloured surfaceGroupOverlapStacking orderGroup count and actionsGroup overflow buttonGroup with interactive avatarsSkeletonRTLIdentity colourAPI ReferenceAvatarAvatarGroupAvatarGroupCountAvatarSkeletonAvatarStatusPositionExportsSizesMigrating from the previous APIAccessibilityWhen to use which