HyAvatar
Usage examples on this page are written for Lit / plain HTML (
<hy-avatar>). The same component ships asHyAvatarin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A flexible avatar component for displaying user profile images or initials.
Key Features
- Display profile images or initials via explicit showInitials prop
- Multiple size options (small, medium, large); an interactive avatar grows to the touch target on coarse pointers
- Two shapes:
circlefor people,square(rounded by the design system's radius) for anything else - An image by default;
interactivemakes it a button (Enter / Space / click) - Event-driven image loading feedback
- High contrast mode support
- Touch-friendly sizing: an
interactiveavatar grows to the touch-target tokens (48px minimum) on coarse-pointer devices; a static one keeps its size - Reduced motion support for accessibility
Accessibility: built in
- Static by default:
role="img", no Tab stop, no hover — an image, not a control interactive:role="button", a Tab stop, Enter / Space / click fireactivate- High contrast mode support with enhanced borders
- An interactive avatar takes the touch-target size on coarse pointers; a static one keeps its drawn size
Accessibility: your part
- Set
alton a photo avatar to the person's name, never "avatar" or "profile picture". - On an initials avatar, set
aria-labelto the full name. Two letters read poorly. - When the name is shown as text right beside the avatar, leave
altempty so it is not read twice. - On a
markedavatar, put the reason in the name ("Ada Lindqvist, editing"). The ring itself is not announced. - In permission lists, audit trails and other places where identity matters, show the name as text next to the avatar.
- When a presence change matters in the moment, announce it in your own live region. The avatar does not announce it.
Display Control
The photo when it has loaded, the initials otherwise:
- With
src, the initials show until the photo has loaded (a slow photo never reads as an empty circle), then the photo covers them; if it fails to load, the initials stay and no broken image is drawn. - With no
src, the initials. showInitialsforces the initials whateversrcdoes.- A failed photo with no initials leaves the plain circle (and a dev warning).
CSS Custom Properties
The component integrates with the design system through CSS custom properties:
--hy-avatar-background-identity-rest: Background color at rest--hy-avatar-background-identity-hover: Background color on hover--hy-avatar-background-identity-pressed: Background color when pressed--hy-avatar-background-identity-focus: Background color when focused--hy-avatar-background-neutral-disabled: Background color when disabled--hy-avatar-foreground-inverse-rest: Initials text color--hy-avatar-foreground-default-disabled: Initials text color when disabled--hy-avatar-radius-pill-md: Border radius of the circle--hy-avatar-radius-container-sm: Border radius of the square--hy-avatar-control-height-xs,--hy-avatar-control-height-sm,--hy-avatar-control-height-md: the small / medium / large size — the chip's ladder, control heights one step down (24 / 32 / 40 px at standard density), so an avatar lines up with the controls beside it and follows density with them
Categorical Coloring
When category-group is set the avatar paints itself with a color from the named palette in the active DS's roles.categorical map (--hy-background-cat-{group}-{N}-*). identity is hashed via hashToCategorical against the group's --hy-cat-{group}-count; slot-index is an explicit 1-indexed slot in the group and beats identity when both are set.
The component renders a categorical color only when all three preconditions hold: category-group is set, the DS defines that group, and the group has at least one ramp. Any failure → default (gray) styling, no console noise. Out-of-range slot-index (above the group's count, below 1, NaN) also falls back to gray.
Presence
presence (available | away | busy | offline) draws a dot at the bottom-end corner — on the circle's rim, or on the square's corner arc — about 0.3 of the avatar's size, following the size and the coarse-pointer sizes. Each status has its own shape, so it never rests on color alone: available a disc, busy a disc with a bar, away a crescent, offline a ring. The status joins the accessible name ("Ada Lindqvist, Busy"); presence-label localizes it. The dot is not announced when it changes. Inside a group or on a surface other than the page, re-point --hy-avatar-presence-background-surface-base so the cutout matches.
Marked
marked draws a brand ring around the avatar, with a transparent gap between the ring and the face, to single one person out: the one editing, the one speaking, the one you follow. The ring is drawn outside the avatar, so marking it never moves the layout, and the page shows through the gap on any surface. In an hy-avatar-group the stack's own ring turns brand instead, with no gap, so the overlap stays the same. The mark adds nothing to the accessible name: say what it means in the name ("Ada Lindqvist — editing"). Under forced colors the ring is Highlight; on a focused interactive avatar the focus ring moves outside it.
When to use
- A person or an organization next to their name or content: in a list, a comment, an account menu.
Do
- Use one size for the avatars of one list.
When not to use
- Several people shown together as the members of a team or thread:
hy-avatar-group. - A product shot or an illustration: a plain
<img>. A symbol for a concept:hy-icon. - A role, a permission or a workflow state: a
hy-badgeorhy-chipwith text beside the avatar. Only presence goes on the avatar itself. - A person as a removable token in a field or filter row:
hy-chipwith the avatar in itsprefixslot. - A photo avatar without
initials: a photo that fails leaves an empty disc. Setinitialsbesidesrc.
Examples
Basic Avatar with Image
<hy-avatar src="/path/to/image.jpg" alt="John Doe" size="medium"> </hy-avatar>Avatar with Initials
<hy-avatar initials="JD" show-initials aria-label="John Doe" size="medium"> </hy-avatar>Toggle Between Image and Initials
<!-- Programmatically control which displays -->
<hy-avatar src="/path/to/image.jpg" alt="John Doe" initials="JD" show-initials size="large">
</hy-avatar>Disabled Avatar
<hy-avatar src="/path/to/image.jpg" alt="Inactive User" disabled size="medium"> </hy-avatar>Interactive Avatar with Event Handlers
<hy-avatar
interactive
src="/path/to/image.jpg"
alt="John Doe"
size="medium"
@activate="${handleClick}"
@image-load="${handleLoad}"
@image-error="${handleError}"
>
</hy-avatar>Presence
<hy-avatar src="/ada.jpg" alt="Ada Lindqvist" presence="busy"></hy-avatar>
<hy-avatar
initials="BK"
show-initials
aria-label="Ben Kato"
presence="away"
presence-label="Abwesend"
></hy-avatar>Marked
<hy-avatar initials="AL" aria-label="Ada Lindqvist — editing" marked></hy-avatar>Different Sizes
<hy-avatar src="/path/to/image.jpg" alt="User" size="small"></hy-avatar>
<hy-avatar src="/path/to/image.jpg" alt="User" size="medium"></hy-avatar>
<hy-avatar src="/path/to/image.jpg" alt="User" size="large"></hy-avatar>API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
src | src | string | undefined | — | — |
alt | alt | string | '' | — |
initials | initials | string | undefined | — | — |
size | size | 'small' | 'medium' | 'large' | 'medium' | — |
shape | shape | 'circle' | 'square' | 'circle' | Shape — circle for people, square for anything that isn't a person. |
emphasis | emphasis | 'solid' | 'outlined' | 'solid' | — |
presence | presence | 'available' | 'away' | 'busy' | 'offline' | — | Presence dot — available | away | busy | offline; unset draws none. |
presenceLabel | presence-label | string | undefined | — | Name of the status added to the accessible name. Unset, it is the English word for the presence: Available, Away, Busy, Offline. Use for internationalization. |
marked | marked | boolean | false | Marks this avatar with a brand ring (the one editing, speaking, followed). Visual only — nothing is added to the accessible name. |
disabled | disabled | boolean | false | — |
interactive | interactive | boolean | false | A button: role="button", a Tab stop, and Enter / Space / click fire activate, with hover and press fills. Unset, the avatar is an image — no Tab stop, no hover, no activate. |
ariaLabel | aria-label | string | null | null | — |
ariaHaspopup | aria-haspopup | string | null | null | The popup an interactive avatar opens. hy-menu-button sets it (and aria-expanded) on its slotted trigger's host; the avatar's role="button" is inside its shadow root, so both are forwarded there — without it a screen reader heard a plain button, never "menu, collapsed". Ignored on a static avatar. |
ariaExpanded | aria-expanded | string | null | null | Whether the popup an interactive avatar opens is open; forwarded like aria-haspopup. |
showInitials | show-initials | boolean | false | — |
categoryGroup | category-group | string | undefined | — | — |
identity | identity | string | undefined | — | — |
slotIndex | slot-index | number | undefined | — | — |
Events
| Event | Detail | Description |
|---|---|---|
activate | — | Fired by an interactive avatar on click, Enter or Space (not when disabled) |
image-load | — | Fired when image successfully loads |
image-error | — | Fired when image fails to load |
CSS Parts
| Part | Description |
|---|---|
base | The avatar's base wrapper element |
image | The avatar's image element |
initials | The avatar's initials text element |
presence | The presence dot |
CSS Custom Properties
The documented hooks. Every override hook the component reads — one per token it paints — is listed in @whitespaceux/harmony/theme-api.json, and @whitespaceux/harmony/component-bindings.json (the Harmony MCP get-bindings tool) says which part and state each one paints.
| Property | Default | Description |
|---|---|---|
--hy-avatar-background-neutral-disabled | var(--hy-background-neutral-disabled) | Background color when disabled |
--hy-avatar-foreground-default-disabled | var(--hy-foreground-default-disabled) | Initials text color when disabled |
--hy-avatar-radius-pill-md | var(--hy-radius-pill-md) | Border radius of the circle |
--hy-avatar-control-height-xs | var(--hy-control-height-xs) | Size of the small avatar |
--hy-avatar-control-height-sm | var(--hy-control-height-sm) | Size of the medium avatar (the default) |
--hy-avatar-control-height-md | var(--hy-control-height-md) | Size of the large avatar |
--hy-avatar-caption-md-font-family | var(--hy-caption-md-font-family) | Font family of the small avatar's initials |
--hy-avatar-caption-md-font-size | var(--hy-caption-md-font-size) | Font size of the small avatar's initials |
--hy-avatar-caption-md-font-weight | var(--hy-caption-md-font-weight) | Font weight of the small avatar's initials |
--hy-avatar-caption-md-letter-spacing | var(--hy-caption-md-letter-spacing) | Letter spacing of the small avatar's initials |
--hy-avatar-control-label-sm-font-family | var(--hy-control-label-sm-font-family) | Font family of the medium avatar's initials |
--hy-avatar-control-label-sm-font-size | var(--hy-control-label-sm-font-size) | Font size of the medium avatar's initials |
--hy-avatar-control-label-sm-font-weight | var(--hy-control-label-sm-font-weight) | Font weight of the medium avatar's initials |
--hy-avatar-control-label-sm-letter-spacing | var(--hy-control-label-sm-letter-spacing) | Letter spacing of the medium avatar's initials |
--hy-avatar-control-label-md-font-family | var(--hy-control-label-md-font-family) | Font family of the large avatar's initials |
--hy-avatar-control-label-md-font-size | var(--hy-control-label-md-font-size) | Font size of the large avatar's initials |
--hy-avatar-control-label-md-font-weight | var(--hy-control-label-md-font-weight) | Font weight of the large avatar's initials |
--hy-avatar-control-label-md-letter-spacing | var(--hy-control-label-md-letter-spacing) | Letter spacing of the large avatar's initials |
--hy-avatar-motion-transition-hover-duration | var(--hy-motion-transition-hover-duration) | Transition duration for hover and focus state feedback |
--hy-avatar-motion-transition-hover-easing | var(--hy-motion-transition-hover-easing) | Transition easing for hover and focus state feedback |
--hy-avatar-radius-container-sm | var(--hy-radius-container-sm) | Corner of the square shape — the same at every size |
--hy-avatar-presence-background-success-indicator-rest | var(--hy-background-success-indicator-rest) | Fill of the available dot |
--hy-avatar-presence-background-warning-indicator-rest | var(--hy-background-warning-indicator-rest) | Fill of the away dot |
--hy-avatar-presence-background-danger-indicator-rest | var(--hy-background-danger-indicator-rest) | Fill of the busy dot |
--hy-avatar-presence-border-strong-rest | var(--hy-border-strong-rest) | Ring of the offline dot |
--hy-avatar-presence-background-surface-base | var(--hy-background-surface-base) | Cutout around the dot (and the busy bar) — re-point it on a surface other than the page |
--hy-avatar-presence-stroke-md | var(--hy-stroke-md) | Width of the cutout around the dot |
--hy-avatar-border-brand-rest | var(--hy-border-brand-rest) | Color of the mark's ring (marked) |
--hy-avatar-mark-stroke-md | var(--hy-stroke-md) | Width of the mark's ring |
--hy-avatar-mark-gap-stroke-md | var(--hy-stroke-md) | Gap between the mark's ring and the avatar; hy-avatar-group sets it to none |
--hy-avatar-ring-width | — | Width of the ring drawn around the avatar (default 0 — none); set by hy-avatar-group |
--hy-avatar-ring-color | transparent | Color of that ring (default transparent); set by hy-avatar-group |
--hy-avatar-background-identity-rest | — | Disc fill at rest; replaces the categorical / identity color (no identity: the muted surface) |
--hy-avatar-background-identity-hover | — | Disc fill on hover of an interactive avatar |
--hy-avatar-background-identity-pressed | — | Disc fill while an interactive avatar is pressed |
--hy-avatar-background-identity-focus | — | Disc fill while an interactive avatar has keyboard focus |
--hy-avatar-foreground-inverse-rest | — | Initials color on the solid disc (inverse ink while an identity color is set) |
--hy-avatar-foreground-outlined-rest | — | Initials color in emphasis="outlined" (the identity color while one is set) |