Skip to content
Folio

Display

Avatar

A person, as a circle, with initials until the image lands.

Usage

TSX
import { Avatar } from '@misoto22/design'

Notes

A person, as a circle.

Wraps Radix so the fallback appears only after the image has actually failed or is still loading — a hand-rolled onError swap flashes the initials on every render before the cache answers.

The ROOT carries the accessible name, not the image. alt on the image reaches the DOM only when src does, and the initials are aria-hidden, so an avatar with no photograph announced nothing at all however carefully alt was written — and a user list where photographs are optional is full of them.

Anatomy

Avatar anatomy
ElementDescription
CirclerequiredThe Radix root: a --rule hairline over --stone, overflow hidden, and one of three fixed squares — 28px, 36px or 48px. It carries role="img" and alt as its own name, because it is the one element in here that is always rendered.
ImageRendered only when src is given, object-cover so a portrait crops to the circle instead of distorting. Its own alt is empty on purpose — the circle around it is what names the person, and a second name here is the same person read twice.
InitialsThe Radix fallback: mono, uppercase, --ink-3-aa, and aria-hidden. Radix shows it while the image is loading and once it has failed, which is why it does not flash on every render the way a hand-rolled onError swap does.

Best practices

Do

  • Write alt for the person, not for the row: the circle carries it whether or not a photograph ever arrives, so a list where photographs are optional announces every person rather than every second one.
  • Pass initials that hold up on their own: fallback is required because the image is the optional half, and most rows of a real list render as this element rather than as a photograph.
  • Let Radix own the swap rather than reaching for onError — it flips to the fallback only once the image has actually failed, which is what keeps the initials from appearing for a frame before the cache answers.

Don’t

  • The avatar is not the control. sm is 28px and md is 36px, both under the 44px pointer-target floor (WCAG 2.5.8), and the root is a <span> nothing has made focusable — an account menu made by hanging onClick on it is unreachable and undersized at once.
  • children is deliberately omitted from the props: the image and the fallback are the only two things that go in the circle, and the root is overflow-hidden, so a presence dot placed inside it is clipped by the very border that makes it round.

Examples

sizes

The three fixed squares — 28px, 36px and 48px — with no image behind any of them, which is what most rows of a real list render as. fallback is required for that reason: it is the half that is always there, and the image is the optional one. None of these sizes is a control, and two of them are under the 44px pointer target a control would need.

a name beside it

The arrangement that makes the avatar readable: the name printed next to it. alt is deliberately empty here because the text beside it already says who this is — and alt only reaches the DOM when src does, while the initials are aria-hidden, so a person with no photograph has no accessible text in this circle at all, however carefully alt was written.

  • Henry ChenDesign systems
  • Rui WangInfrastructure

inside the control

An account control with the avatar inside it, rather than an onClick hung on the circle. The root is a span nothing has made focusable and sm is 28px, under the 44px pointer floor — so an avatar wired up as a menu trigger is unreachable by keyboard and undersized at once. The button brings the target, the focus ring and the accessible name; the circle stays a picture.

Accessibility

  • alt names the circle itself, as role="img", so an avatar with no photograph still says who it is.
  • An empty alt is correct when the name is already printed beside it: the circle then takes no role at all rather than an unnamed one, and leaves the tree entirely.
  • The initials are aria-hidden — read aloud beside a name the root already gives, they are noise.