Avatar
stimeo--avatar
Shows a user's picture and swaps in the fallback you prepared when it cannot load.
Shows a user's picture, and swaps in the fallback you prepared when the image fails or no image was given. Whether it is empty, loading, loaded or failed is readable from CSS, so the waiting state can be styled too. The name lives on the outer frame while the image and the fallback are kept out of the reading order, so a screen reader says the name once whichever side is showing. Shape, size and colour are this demo's CSS. Generating initials or picking colours is not part of it.
Keyboard
This component has no keyboard interactions of its own.
<%# Markup for the avatar demo.
The accessible name is owned solely by the container (role="img" + aria-label);
the inner <img> and fallback use aria-hidden="true" to avoid double announcement.
The library only tracks the source and its load state, exposing empty / loading /
loaded / error on data-state; shape, color, and size are drawn by demo.css. %>
<div class="avatar-demo">
<%# 1. An avatar that loads successfully. The src is a self-contained data-URI SVG that
draws a person silhouette (a picture, not letters) so the "loaded image" state reads
clearly different from the initials fallback below. %>
<figure class="avatar-demo__item">
<span
class="avatar"
data-controller="stimeo--avatar"
role="img"
aria-label="<%= t("components.avatar.demo.name_ok") %>"
data-stimeo--avatar-src-value="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='96'%20height='96'%3E%3Cdefs%3E%3ClinearGradient%20id='g'%20x1='0'%20y1='0'%20x2='0'%20y2='1'%3E%3Cstop%20offset='0'%20stop-color='%236366f1'/%3E%3Cstop%20offset='1'%20stop-color='%234338ca'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect%20width='96'%20height='96'%20fill='url(%23g)'/%3E%3Ccircle%20cx='48'%20cy='38'%20r='17'%20fill='%23eef2ff'/%3E%3Cpath%20d='M20%2084%20a28%2028%200%200%201%2056%200%20Z'%20fill='%23eef2ff'/%3E%3C/svg%3E">
<img
class="avatar__image"
alt=""
aria-hidden="true"
data-stimeo--avatar-target="image"
data-action="load->stimeo--avatar#onLoad error->stimeo--avatar#onError">
<span
class="avatar__fallback"
aria-hidden="true"
hidden
data-stimeo--avatar-target="fallback"
>AU</span>
</span>
<figcaption><%= t("components.avatar.demo.caption_ok") %></figcaption>
</figure>
<%# 2. An avatar that fails to load (nonexistent path → fallback initials). The browser
logs an expected 404 for the missing image — that is the point: it triggers the error
→ fallback path the library handles. %>
<figure class="avatar-demo__item">
<span
class="avatar"
data-controller="stimeo--avatar"
role="img"
aria-label="<%= t("components.avatar.demo.name_fallback") %>"
data-stimeo--avatar-src-value="/this-image-does-not-exist.jpg">
<img
class="avatar__image"
alt=""
aria-hidden="true"
data-stimeo--avatar-target="image"
data-action="load->stimeo--avatar#onLoad error->stimeo--avatar#onError">
<span
class="avatar__fallback"
aria-hidden="true"
hidden
data-stimeo--avatar-target="fallback"
>JD</span>
</span>
<figcaption><%= t("components.avatar.demo.caption_fallback") %></figcaption>
</figure>
<%# 3. An avatar with no image target at all. Both targets are optional, so a
fallback-only avatar is valid and settles in the empty state. %>
<figure class="avatar-demo__item">
<span
class="avatar"
data-controller="stimeo--avatar"
role="img"
aria-label="<%= t("components.avatar.demo.name_empty") %>">
<span
class="avatar__fallback"
aria-hidden="true"
data-stimeo--avatar-target="fallback"
>GU</span>
</span>
<figcaption><%= t("components.avatar.demo.caption_empty") %></figcaption>
</figure>
</div>
/* Presentation CSS for avatar. The library only tracks load state and toggles
data-state / hidden, so shape, color, size, and the fallback look are all here. */
.avatar-demo {
display: flex;
gap: 2rem;
flex-wrap: wrap;
}
.avatar-demo__item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
margin: 0;
/* Cap the column width so a longer caption wraps under the avatar (centered)
instead of stretching the item and breaking the row layout. */
max-width: 12rem;
font-size: 0.85rem;
color: var(--color-text-muted);
text-align: center;
}
.avatar-demo__item figcaption {
text-wrap: balance;
}
/* The container owns the circular clip; the inner image / fallback are layered inside. */
.avatar {
position: relative;
display: inline-grid;
place-items: center;
width: 96px;
height: 96px;
border-radius: 50%;
overflow: hidden;
background: var(--surface-subtle);
box-shadow: 0 0 0 2px var(--surface-subtle), 0 1px 4px rgba(15, 23, 42, 0.2);
}
.avatar__image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Fallback initials. Visible only once the hidden attribute is removed. */
.avatar__fallback {
font-size: 2rem;
font-weight: 600;
color: var(--white);
background: var(--slate-500);
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
/* data-state is set by the library. Example: a subtle pulse while loading. */
.avatar[data-state="loading"] {
animation: avatar-pulse 1.2s ease-in-out infinite;
}
@keyframes avatar-pulse {
50% {
opacity: 0.6;
}
}
@media (prefers-reduced-motion: reduce) {
.avatar[data-state="loading"] {
animation: none;
}
}
This demo needs no consumer-side JS (the controller handles the behavior).
These demo styles use shared design tokens (light + dark). Copy the shared styles too, then toggle data-theme on your root element for dark mode.
The data-* attributes you add to your own HTML to wire this component. Put the data-controller below on a root element, then place its targets / values / actions inside that element.
On the root element
data-controller="stimeo--avatar"
Targets
| Name | Description | Attribute |
|---|---|---|
image
|
Optional <img> whose load/error is watched and which is shown on success. |
data-stimeo--avatar-target="image" |
fallback
|
Optional author-provided element shown when the image fails or has no source. | data-stimeo--avatar-target="fallback" |
Values
| Name | Description | Attribute |
|---|---|---|
src
|
When the Value attribute is present it owns <img src> (including an explicit empty value); when absent, a directly authored src is used. |
data-stimeo--avatar-src-value |
Actions
| Name | Description | Action |
|---|---|---|
onError
|
Swaps to the fallback when the image fails to load and emits the error event. | stimeo--avatar#onError |
onLoad
|
Reveals the image once it has loaded successfully. | stimeo--avatar#onLoad |
Events
| Name | Description | Event |
|---|---|---|
error
|
Dispatched only for a native failure from the current image; detail carries the attempted src. | stimeo--avatar:error |
State hooks
The library only manages these ARIA/data attributes and custom properties. Your CSS reads them to render the look — selectors like [aria-selected], [aria-expanded], or var(--stimeo--…) hook into this state.
| Hook | Target | Meaning |
|---|---|---|
data-state |
Container | "empty" / "loading" / "loaded" / "error". |
hidden |
Image / Fallback | Only the visible side is shown. |