Avatar
Represent an entity with a correctly named image or fallback, while keeping loading, action and identity authority separate.
Representation is not verification
Use Avatar as a compact visual reference to an entity. Give it meaningful nearby identity text, or an accessible name when it carries information independently. A picture, initial or badge does not establish who a person is, their community authority or permission.
An Avatar is not a button or menu trigger. Put it inside a properly named real control when there is an action. If that control already has a visible name, make the avatar decorative to avoid a redundant announcement.
Anatomy and visual specifications
| Part | Actual contract |
|---|---|
| Outer representation | Native div/ref with circular clip, muted surface, shrink-resistant geometry and no automatic hover ring/action cue. Meaningful labels supply role=img; unnamed representations are decorative by default. |
| Sizes | sm=32px, md=40px, lg=48px, xl=64px. These are image dimensions, not guaranteed interactive target sizes. Surrounding controls need their own target. |
| Image loader | Pinned native-image preloader owns loading/error status. The actual img renders after loading, fills the reserved representation and uses object-fit:cover. |
| Fallback | Installed fallback shows while the image is absent/loading/failed. Its text is clipped to the image dimensions and hidden from duplicate accessibility naming. |
| Name | The outer name remains available across image and fallback states. The internal image is decorative within that representation, rather than disappearing with an alt-only name on error. |
| Source replacement | A key scoped to source/retry/policy resets inner loader state while the outer div/ref stays stable. Unmounted loader callbacks ignore stale completion. |
Working source, error and retry flow
Load an actual local geometric SVG illustration, request a missing local source, retry or clear. The browser’s image status drives visible feedback and fallback. Replacing a failed source loads successfully instead of retaining the previous permanent error flag.
The illustration is generated geometry, not a person’s photograph or verified identity. Example A is a named representation. Example B is decorative inside a named button that changes a real page-memory selection; the Avatar itself creates no action. This example performs no remote profile save.
Local geometric example A. This illustration is not a person’s photograph or verified identity.
Actual image state: idle.
The labelled representation retains its name during loading/error. Missing or failed sources use the explicit glyph; source replacement and Retry create a fresh loader.
Actual local example B unselected.
Example B’s adjacent button text supplies the action name; its avatar is decorative. A bare avatar adds no button or menu behavior.
'use client';
import {useState,type CSSProperties} from 'react';
import {Avatar,Button,type AvatarLoadingStatus} from '@mobtranslate/ui';
const action:CSSProperties={minWidth:0,maxWidth:'100%',minHeight:44,height:'auto',whiteSpace:'normal',overflowWrap:'anywhere'};
const illustration=`data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" fill="#3b342e"/><circle cx="32" cy="32" r="24" fill="#e79554"/><text x="32" y="41" text-anchor="middle" font-size="28" fill="#21150a">A</text></svg>')}`;
export function AvatarSourceExample(){
const [mode,setMode]=useState<'none'|'image'|'error'>('none');
const [retry,setRetry]=useState(0);
const [size,setSize]=useState<'sm'|'md'|'lg'|'xl'>('md');
const [status,setStatus]=useState<AvatarLoadingStatus>('idle');
const [selected,setSelected]=useState(false);
const src=mode==='image'?illustration:mode==='error'?'/__styleguide_avatar_missing__.svg':undefined;
return <div data-avatar-lab className="space-y-4"><div className="flex flex-wrap gap-3"><Button style={action} type="button" variant="outline" onClick={()=>{setMode('image');setRetry(v=>v+1);setStatus('loading');}}>Load local avatar illustration</Button><Button style={action} type="button" variant="outline" onClick={()=>{setMode('error');setRetry(v=>v+1);setStatus('loading');}}>Use missing local avatar source</Button><Button style={action} type="button" variant="outline" onClick={()=>{setRetry(v=>v+1);setStatus(src?'loading':'idle');}}>Retry current avatar source</Button><Button style={action} type="button" variant="outline" onClick={()=>{setMode('none');setStatus('idle');}}>Clear avatar source</Button></div>
<label>Avatar size <select data-avatar-size className="mt-input" value={size} onChange={event=>setSize(event.target.value as 'sm'|'md'|'lg'|'xl')}><option value="sm">Small</option><option value="md">Medium</option><option value="lg">Large</option><option value="xl">Extra large</option></select></label>
<div className="flex flex-wrap items-center gap-4"><Avatar data-avatar-primary size={size} src={src} alt="Local geometric example A" aria-label={undefined} fallback="A" retryKey={retry} onLoadingStatusChange={setStatus} referrerPolicy="no-referrer" /><p>Local geometric example A. This illustration is not a person’s photograph or verified identity.</p></div>
<p role="status" data-avatar-status>Actual image state: {status}.</p>
<p>The labelled representation retains its name during loading/error. Missing or failed sources use the explicit glyph; source replacement and Retry create a fresh loader.</p>
<Button style={action} type="button" variant="outline" aria-pressed={selected} onClick={()=>setSelected(v=>!v)}><Avatar size="sm" fallback="B" alt="" /><span className="ms-2">Toggle local example B</span></Button><p data-avatar-selection>{selected?'Actual local example B selected.':'Actual local example B unselected.'}</p>
<p>Example B’s adjacent button text supplies the action name; its avatar is decorative. A bare avatar adds no button or menu behavior.</p>
</div>;
}
Complete public API
| Prop | Ownership |
|---|---|
| size | sm, md, lg or xl; default md. Class/style overrides can change geometry, so verify the actual result. |
| src | Image source string. A source/policy change creates a fresh installed loader; it does not certify provenance, identity, rights or safety. |
| alt | Meaningful representation name when needed. Empty/omitted alt with no other name makes the root decorative; use nearby visible identity text. |
| aria-label / aria-labelledby | Native outer naming alternatives. Explicit aria-label takes precedence over alt in the wrapper; an actual labelled-by relationship can name the root. |
| fallback | Explicit glyph string, preferred for meaningful identity conventions. Without it, a best-effort first Unicode code point of alt is uppercased, or ? is used. This is not a grapheme/initials/identity engine. |
| retryKey | String/number revision, default 0. Changing it remounts the loader for the same source. This does not promise HTTP-cache bypass or cancellation of the browser’s network request. |
| onLoadingStatusChange | Callback with exported AvatarLoadingStatus: idle/loading/loaded/error. The installed image reports real loading changes; with no image mounted, the parent owns its absent/idle status. |
| referrerPolicy / crossOrigin | Native policy options forwarded to the actual installed preloader and image. Policy changes reset loading. Use the consuming application’s privacy/authentication/asset contract. |
| className / style / native attributes / ref | Native div attributes/handlers and HTMLDivElement ref. Wrapper owns image/fallback composition; arbitrary children are not a replacement-media API. Preserve useful outer naming when customizing. |
Loading and failure invariants
- Keep reserved geometry and a useful identity/name while loading. A fallback glyph is not evidence that an image loaded successfully.
- Failure should preserve the entity’s meaning and offer a reachable recovery path when the task needs the image. Repeated retries must remain real loader attempts, not a label that says loading forever.
- Source replacement must clear the old error and must not display a stale prior image as the new entity. The keyed installed loader keeps that lifetime separate from the stable outer representation.
- Changing retryKey creates a fresh loader, but browser caching and credential policy still apply. A remote signed URL or asset failure needs the application’s real recovery/refresh policy.
- Avoid deriving cultural names, initials or authority from a file name or portrait. Supply reviewed display text and useful fallback explicitly. Unicode code-point fallback is only a visual convenience.
Accessibility, interaction and cropping
- A standalone informative Avatar needs a stable outer name through loading/error. Decorative avatars beside visible text should avoid repeating that text.
- Fallback and inner image are visual parts of one named representation. Do not rely on initials alone as a meaningful alternative or claim they are hidden when the rendered markup does not hide them.
- A bare div is not an actionable control. Use Button/link/menu-trigger semantics, a meaningful action name, real outcome and adequate target outside the image dimensions.
- Cover cropping can remove meaningful image context. Provide complete source/attribution elsewhere when recognition, evidence or identification needs it.
- Observe contrast for fallback text on its actual surface, long glyph clipping, forced colors, source orientation and zoom. Spoken announcements and image interpretation require separately scoped evidence.
Migration and quality gates
The old implementation stored one permanent hasError flag and never reset it on src changes. Its guide also claimed aria-hidden fallbacks that the actual markup did not supply. The installed loader and named outer representation now keep source lifetime and fallback accessibility explicit.
- Run test:avatar, checked-source/token freshness, lint, UI/styleguide types, production build and the complete route/fragment crawl. Verify actual load/error/retry/source replacement, stable names, decorative action composition, all sizes and narrow layouts.
- No external avatar service or third-party demonstration requests are needed by this example. Actual profile-photo rights, consent, availability and every consuming authentication/profile workflow remain their own acceptance work.