Represent a person, a team or a thing with an image, initials or an icon, with an indicator on a corner or an edge, a ring and group stacking that hold at every size.
An avatar is a picture, not a control. Most of the work here is deciding what it should be announced as, and what has to sit around it when it needs to be operable.
Avatar is not a control
AvatarProps is a closed list and it does not extend HTMLAttributes: there is no role, tabIndex, aria-label or key handler to put beside an onClick. A bare clickable avatar is unreachable by keyboard and unnamed to a screen reader, and no attribute at the call site can fix it.
Put it inside a Button, which brings the element, the tab stop, the focus ring and Enter/Space with it.
Or use Button's own avatar prop, which renders the same face inside a real button with the gaps already correct.
Name the action on that button, not only the person: “Open your account menu”, “Remove Ada Lovelace from this project”.
The same applies to anything in the indicator slot. A camera or remove affordance in the corner is a Button; a presence dot is a plain span and must not be focusable.
A reveal-on-hover control has to reveal on focus as well. A remove button that only appears on group-hover does not exist for a keyboard.
What it is announced as
The accessible name depends on which content route the avatar took, and only one of the three has a name channel of its own.
Image.alt is forwarded to the underlying <img>. Give every image avatar one: there is no text to fall back on, so without it the avatar is announced as an unlabeled image.
Image beside its own name. Use alt="", which drops it from the accessibility tree. The picture carries nothing the text next to it does not already say, and repeating it is noise.
Initials. They are real text, so they are read out, letter by letter or as a word depending on the reader. That is rarely useful on its own: keep the full name visible, or wrap the avatar in an element that carries it.
Icon.alt only applies to the src branch, so an icon avatar has no name at all. Its meaning has to come from the row it sits in, or from a label on a wrapping element.
Do not write 'avatar' or 'profile picture' as alt text for a known person. Write the name, or write nothing.
Groups and tooltips
A stacked group hides the names visually, so a Tooltip per avatar is the usual fix. The Tooltip wraps the Avatar, because it needs a child to attach a trigger to and AvatarProps carries no title.
A Tooltip is not a label. It does not exist for touch and is not reliably announced, so the names still have to be reachable somewhere: the overflow popover, a nearby list, or text.
The +n overflow chip announces its own text, which is a count and not a list. Let its popover carry the names.
In a picker, give each person one focus stop, not one per avatar plus one per label.
Status and presence
Color alone is not an accessible signal, and a presence dot is the most common place that rule gets broken.
Say the state three ways where it matters: the hue, the shape of the glyph, and words. Two people in ten cannot separate the first from a neighboring hue.
Use the four semantic status colors rather than palette shades, so a presence dot matches every Alert, Tag and Result in the product.
If the state can change while the page is open, announce the change in a live region. Nothing about repainting a dot is announced on its own.
Contrast
The colorless default is colorFillSecondary under colorTextSecondary, which clears WCAG AA with room in both modes. Ant Design's own white label on colorTextPlaceholder is unreadable in light mode.
variant="solid" puts white on the ramp's mid tone and misses AA on ten of the twelve presets, with gold and yellow the worst. Use filled on a warm hue, or a neutral ramp, whose solid fill is darker.
Initials are small text at every size, because textFontSize does not grow with the named sizes. Check them at the smallest size you ship, not at the largest.
The ring is transparent outside a group. If you use it to carry a state, give it a color you have checked against both the page and the avatar.
Re-check both modes after any token change. The two ramps darken in opposite directions.
Motion and decoration
Gate hover and expand animations behind motion-safe:. An expandable group that fans out on hover is the kind of motion a reader may have asked to turn off.
A grayscale treatment is decoration, not information. If it means “inactive”, say so somewhere that is readable.
A story ring that means “unseen” needs a second signal too, for the same reason.