Avatar
A square image slot with a text fallback underneath. The image paints over the fallback and hides itself when it has nothing to paint, revealing the initials. Class + data-attribute API mirrors the Badge: one .x-avatar class plus data-variant, data-size, data-accent.
Install
import "elements-kit/ui/styles.css";
import "elements-kit/ui/styles/palette/gray.css";import "elements-kit/ui/styles/neutral/gray.css";import "elements-kit/ui/styles/palette/iris.css";import "elements-kit/ui/styles/accent/iris.css";
import "elements-kit/ui/avatar/avatar.css";API
<span class="x-avatar" data-size="3" data-variant="soft"> <span class="x-avatar-fallback">WB</span> <img class="x-avatar-image" src="/wael.jpg" alt="" onerror="this.remove()" /></span>| Attribute | Values |
|---|---|
data-variant | soft (default), solid |
data-size | 1β9 (default 3) β on .x-avatar, the box size |
data-size | 1β3 β on .x-avatar-fallback, the type ratio |
data-accent | any imported color scale (iris, blue, amber, β¦) |
data-high-contrast | modifier β boosts contrast against the page background |
data-status | error β on .x-avatar-image, hides it so the fallback shows |
| Class | Role |
|---|---|
.x-avatar | root β owns size, radius, and clipping |
.x-avatar-fallback | bottom layer β centered initials |
.x-avatar-image | top layer β object-fit: cover |
The fallbackβs own data-size picks how much of the box the text fills β a ratio, not a step off the type scale, so it holds at every avatar size:
data-size | font-size | for |
|---|---|---|
1 | 30% of the box | three letters or more |
2 (default) | 40% of the box | two initials |
3 | 50% of the box | a single letter |
<!-- default β sized for two initials --><span class="x-avatar" data-size="5"> <span class="x-avatar-fallback">WB</span></span>
<!-- one letter, filling more of the box --><span class="x-avatar" data-size="5"> <span class="x-avatar-fallback" data-size="3">W</span></span>
<!-- three letters, filling less --><span class="x-avatar" data-size="5"> <span class="x-avatar-fallback" data-size="1">WBX</span></span>There is no character-count selector in CSS, so the ratio is the authorβs to pick β but the count is known wherever the initials are produced, so it usually falls out of the same expression. Override --avatar-fallback-scale directly for a ratio between the three.
The computed size is floored at --font-size-1, so the smallest avatars stay legible rather than following the ratio down to unreadable text. One consequence: three letters do not fit a data-size="1" avatar at any legible size β 12px text needs roughly 28px of width in a 24px box β and get clipped. Use data-size="3" or larger for three-letter fallbacks, or an icon.
An <svg> in the fallback ignores the type scale and is sized to 60% of the avatar box instead, so a generic person glyph works at every data-size:
<span class="x-avatar" data-size="4"> <span class="x-avatar-fallback"> <svg viewBox="0 0 64 64" aria-hidden="true" style="fill: currentColor"> <path d="β¦" /> </svg> </span></span>Fallback behavior
Both layers are absolutely positioned; .x-avatar-image sits above the fallback via z-index. It hides itself whenever it has nothing to paint:
.x-avatar-image:where(:not([src]), [src=""], [data-status="error"]) { display: none;}So there are three cases, and only one of them needs script:
| Case | Handling |
|---|---|
No src, or src="" | pure CSS β no JS, no error event |
| Load failed (404, CORS, decode) | the error handler sets data-status="error" |
| Still loading | nothing to do β an in-flight <img> paints nothing, so the fallback already shows |
A broken <img> that stays visible is not invisible β every engine paints its own artifact over the fallback:
| Engine | Broken <img alt=""> renders |
|---|---|
| Chrome | broken-image glyph |
| Safari | broken-image glyph |
| Firefox | 1px border ring |
No CSS suppresses all three, so flag the image on error. How, depends on who owns the DOM:
<!-- Static HTML β shortest thing that works --><img class="x-avatar-image" src="/wael.jpg" alt="" onerror="this.remove()" />// Vanilla + strict CSP β one delegated listener, no inline handler.// `error` does not bubble, so capture it.document.addEventListener( "error", (event) => { const img = event.target; if (img instanceof HTMLImageElement && img.matches(".x-avatar-image")) { img.dataset.status = "error"; } }, true,);// React / Vue / Svelte β render the attribute, never mutate the node.// `this.remove()` leaves the vdom believing the <img> is still mounted, and// the next re-render puts it back.const [failed, setFailed] = useState(false);
<img class="x-avatar-image" src={src} alt="" data-status={failed ? "error" : undefined} onError={() => setFailed(true)}/>;Setting the attribute is preferable to removing the element in every context that re-renders: it is non-destructive, so a retry or a changed src can clear it.
Keep alt="" whichever tier you use β the initials are decorative and the name belongs on the root, see Accessibility.
Sizing
data-size | size |
|---|---|
1 | --space-5 |
2 | --space-6 |
3 | --space-7 |
4 | --space-8 |
5 | --space-9 |
6 | 80px |
7 | 96px |
8 | 128px |
9 | 160px |
Sizes 6β9 are pixel values multiplied by --scaling, so data-scaling on an ancestor still applies. Fallback type and icon size derive from the box, so they follow automatically β only the box size and corner radius are enumerated here.
Variants
soft(default) β tinted fallback background, accent text.solidβ accent fill, high-contrast text.
The variant only paints the fallback; an avatar showing its image looks identical either way.
Radius
Corner radius scales with data-size and follows data-radius on any ancestor. data-radius="pill" makes avatars fully circular:
<div data-radius="pill"> <span class="x-avatar" data-size="4"> <span class="x-avatar-fallback">WB</span> </span></div>Set it on the avatar itself for a one-off circle. Available values: none, small, medium, large, pill.
Theming
Switch the color on a single avatar with data-accent="<color>", or set it on any ancestor to theme the whole subtree. Deriving the accent from the userβs name gives stable per-user colors:
<span class="x-avatar" data-accent="amber" data-variant="soft"> <span class="x-avatar-fallback">AB</span></span>Light/dark flips automatically via the .dark class on a parent β see Light & dark.
Accessibility
.x-avatar ships no role β itβs a visual class. alt="" keeps the image out of the accessibility tree, so the accessible name belongs on the root when the avatar carries information:
| Pattern | Markup |
|---|---|
| Decorative β name appears next to the avatar | <span class="x-avatar">β¦</span> |
| Standalone β avatar is the only identifier | <span class="x-avatar" role="img" aria-label="Wael Bettayeb">β¦</span> |
| Link or button | <a class="unset x-avatar" href="/u/wael" aria-label="Wael Bettayeb">β¦</a> |
Add unset when rendering the avatar as <a> or <button> to neutralise native styling. Initials in the fallback are decorative in every case β they duplicate a name that assistive tech should read in full.