Avatar alt-text convention
Applies to every user display that includes an avatar (comment headers, collaborator/member lists, activity streams, profile headers, user menu triggers, watcher stacks, …).
The rule
One rule, two modes:
-
Name-adjacent →
<img alt="">The username (or display name) is already rendered next to the avatar. The avatar is decorative — a screen reader would double-speak the identity. This is the common case. -
Standalone →
<img alt="{username}">The avatar stands on its own with no adjacent text name. The alt is the username so assistive tech can still identify the user.
If the parent element carries its own accessible name (e.g. a <button>
with aria-label="User menu for {username}"), the avatar inside is
decorative — use the default (name-adjacent) mode. The button's
accessible name owns the identity; duplicating it on the inner <img>
just makes the screen reader repeat itself.
Use the primitive
@/components/ui/Avatar enforces the rule via a standalone prop:
import { Avatar } from '@/components/ui'
// Name-adjacent (default) — alt=""
<div className="flex items-center gap-2">
<Avatar src={user.avatar_url} username={user.username} size={28} />
<span>{user.username}</span>
</div>
// Standalone — alt={username}
<Avatar
src={user.avatar_url}
username={user.username}
standalone
size={32}
/>
When src is empty/null the primitive renders an initial-letter fallback
disc. In standalone mode the disc gets role="img" +
aria-label={username} so the contract holds whether or not the user
has uploaded a photo.
Hand-rolled <img>? Apply the rule directly
If for some reason you can't use the primitive (e.g. a larger preview in
a settings/cropper flow), apply the rule by hand and leave a short
comment next to the alt explaining which mode applies:
{/* Avatar alt-text: username is rendered adjacently, avatar is decorative. */}
<Image src={avatarUrl} alt="" width={64} height={64} className="…" />
The regression test at
web/src/__tests__/components/ui/AvatarAltConvention.test.ts scans
web/src/ for every <img> / <Image> whose src references an
avatar URL and asserts the alt attribute is either empty or an
interpolated expression (i.e. alt={...}). A raw literal like
alt="user photo" will fail the build.