Avatar#

An image with graceful fallback. Every part mirrors the image's load status as data-state="loading|loaded|error", and the runtime hides whichever face is not the current one — the fallback once the image has loaded, the image once it has failed. There is no model: the status is read from the <img> element itself.

Import#

TSX
import { Avatar } from '@sigx/zero/avatar';

Avatar is a compound: Avatar.Root, Avatar.Image, Avatar.Fallback. It is also re-exported from the @sigx/zero root, together with avatarAnatomy, useAvatarContext and the AvatarStatus type ('loading' | 'loaded' | 'error').

Usage#

TSX
import { component } from 'sigx';
import { Avatar } from '@sigx/zero/avatar';
import type { AvatarStatus } from '@sigx/zero/avatar';

const Author = component(({ signal }) => {
    const state = signal({ status: 'loading' as AvatarStatus });

    return () => (
        <Avatar.Root size="md" statusChange={(s) => { state.status = s; }}>
            <Avatar.Image src="/me.png" alt="Andreas Ekdahl" />
            <Avatar.Fallback>AE</Avatar.Fallback>
        </Avatar.Root>
    );
});

Avatar.Image takes the src; Avatar.Fallback is what shows until the image reports in and what stays if it never does. The alt is required — once loaded, the image is the avatar's only accessible representation, because the fallback is hidden. Pass alt="" only for an avatar that is genuinely decorative next to a visible name.

No image at all#

TSX
<Avatar.Root>
    <Avatar.Fallback>?</Avatar.Fallback>
</Avatar.Root>

A missing src resolves to error, and so does an Avatar.Image with no src — the fallback shows and the image is hidden. Changing src later puts the avatar back into loading (or error, if the new value is empty) until the new image settles.

Rendering the image as your own element#

TSX
<Avatar.Image src={url} alt={name} asChild>
    {(p) => <img {...p} loading="lazy" decoding="async" />}
</Avatar.Image>

With asChild the default slot receives the part's attribute bag — src, alt, the onLoad / onError handlers, the hidden and aria-hidden toggles and the ref the component probes. Spread it so the load status can be read off your element.

Server rendering#

Server markup always renders loading: the status resolves on mount, a microtask after the render, so the fallback is what paints until the image reports in. An image the browser has cached settles before hydration attaches the handlers, and the mounted element is the probe for that case — a complete image with pixels is loaded, a complete image with none is one whose error already fired.

Anatomy#

PartElementStatesFlagsNotes
rootspanloading | loaded | errorCarries the variant axes.
imageimgloading | loaded | erroraria-hidden until loaded. Hidden in error (hiddenIn). asChild.
fallbackspanloading | loaded | errorHidden in loaded (hiddenIn). Inside root.

Every part carries data-scope="avatar" and data-part="<part>", and all three carry the same data-state. The two representations swap by presence, not by paint: the runtime sets hidden on the image while error (it would render the broken-image glyph) and on the fallback once loaded. Until the image is what the avatar shows, the fallback is the one accessible representation, so the image is aria-hidden in loading and error — otherwise assistive technology would announce the initials and the alt. See The anatomy contract.

Props#

Avatar.Root#

PropTypeDefaultDescription
statusChangeevent (status: AvatarStatus)Fires whenever the load status changes.
color / size / variant / axes / modsdesign-system vocabularyThe variant axes, rendered as data-* on root.
classstringExtra classes on the root element.

There is no model: the status is display-only, derived from the image, and statusChange is how the app observes it.

Avatar.Image#

PropTypeDefaultDescription
srcstringThe image URL; absent or empty resolves to error.
altstringrequiredThe accessible name once loaded. "" for a decorative avatar beside a visible name.
asChildbooleanfalseRender through the default slot, which receives the part bag.
classstringExtra classes.

Avatar.Fallback#

Only class. Renders the fallback part around its children — initials, an icon, a placeholder.

In the shipped design systems#

Both @sigx/zero-basic and @sigx/zero-daisyui wire color (the eight recommended roles) and size (xsxl) on avatar, so <Avatar.Root color="primary" size="lg"> is styled in both. Neither wires a variant or any mods on the scope; under a design system's /register import the variant prop is therefore absent. See Typed vocabulary.

Because the runtime toggles hidden on image and fallback, a recipe must not set display on either part unconditionally — display: flex on the fallback would override the hidden attribute's display: none and show the initials over a loaded photo. Style &:not([hidden]) instead:

CSS
[data-scope="avatar"][data-part="fallback"]:not([hidden]) {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

A recipe that styles loading and error identically on the fallback is correct — in both the fallback is the face — and the anatomy's hiddenIn is what lets the design-system tooling accept that. The root's tokens are color, radius-selector and size: an avatar rounds like a selector control, and size sets the box.

Skeleton is the other component that holds a shape while something arrives; Badge and Status are what usually sit in an avatar's corner.