Components
Avatar and Rating
Identity for a person, a team or an agent. The fallback is designed rather than tolerated, the tint is deterministic so the same name is always the same colour, and the accessible name is one string.
Examples
import { Avatar, AvatarGroup } from "@luc/ui";Initials are the normal case
The fallback is not a failure state: on a cold cache it is what most readers see. It is rendered immediately when there is no image, and after a short delay when there is one, so a slow network does not flash initials before the picture.
<Avatar name="Ada Lovelace" status="online" />The tint is hashed from the name and picked from six semantic surfaces, so it survives every palette and does not move when a list is re-sorted — which is exactly what a random colour would do.
Groups count in the open
Overflow is real text (+3), not a hidden badge. Give the group a total when the children are paginated.
<AvatarGroup max={3} total={12}>…</AvatarGroup>In a list
The same avatar in a table row, a header and a comment thread, with a read-only Rating beside it — a score that cannot be changed is data, so it is announced as an image rather than a control.
Ada Lovelace
Research
onlineGrace Hopper
Data platform
awayAlan Turing
Modelling
offlineKatherine Johnson
Verification
offline<Card variant="outline" className="divide-y divide-line-subtle">
{people.map((person) => (
<div key={person.name} className="flex items-center gap-3 p-3">
<Avatar name={person.name} status={person.status} />
<span className="flex min-w-0 flex-1 flex-col">
<Text variant="label">{person.name}</Text>
<Text variant="caption" tone="muted">{person.role}</Text>
</span>
<Badge tone={person.status === "online" ? "positive" : "neutral"} size="sm">
{person.status ?? "offline"}
</Badge>
</div>
))}
</Card>Rating is a radiogroup
One tab stop, arrow keys to change the value, Home and End for the ends. Chosen again, the value clears.
<Rating label="Quality" defaultValue={4} />API
| Prop | Type | Default | Notes |
|---|---|---|---|
Avatar name | string | — | Accessible name and the deterministic tint seed. |
Avatar fallback | ReactNode | — | Initials by default; any node to override. |
Avatar size / shape | "xs"…"xl" · "circle" | "rounded" | "square" | "md" · "circle" | Geometry is independent of the name. |
Avatar status | "online" | "away" | "offline" | — | Decorative dot; the label still carries the name. |
AvatarGroup max / total | number | 4 / children | Overflow is rendered as real text, not hidden. |
Rating value / onValueChange | number | — | Controlled or uncontrolled; a radiogroup under the hood. |
Rating allowHalf / readOnly | boolean | — | Half steps, or a score that is data rather than a control. |
Usage
import { Avatar } from "@luc/ui";<Avatar name="Ada Rensselaer" src="/people/ada.jpg" status="online" />Guidelines
Do
- Pass `name` even when `src` is set: the initials are the fallback for a slow image, a broken URL and an export without the file.
- Use `AvatarGroup` with `max` and `total` for a row of participants; the count it adds says how many are behind it.
- Give `label` when the avatar is the only place a person's name appears — otherwise it announces as “Avatar”.
Don't
- Use an avatar as a button: wrap it in the control you want, so the control has its own name and hit area.
Accessibility
The whole avatar is one `role="img"` named by `label`; the image and the initials inside are hidden from the accessibility tree, so the name is announced once. A group is a labelled `role="group"`.
Related