Installation
pnpm dlx shadcn@latest add https://dithercn.dev/r/avatar.jsonUsage
import { Avatar, AvatarGroup } from "@/components/dithercn/avatar";
export function Owner() {
return <Avatar name="ada@acme.dev" label="Ada Lovelace" status="online" />;
}
export function Viewers({ names }: { names: string[] }) {
return (
<AvatarGroup max={4}>
{names.map((name) => (
<Avatar key={name} name={name} />
))}
</AvatarGroup>
);
}name seeds a small random generator that fills half of a 5 by 5 grid of blocks, and the other half mirrors it. A block is more likely to be on near the centre, so every face is a compact mark rather than noise, and the middle column always has at least two blocks. Each block is full or half tone, and the fill decides how half tone is drawn. Behind the face sits a disc of sparse dither in the same colour, densest at the top left as if lit, and kept one cell clear of the face so the mark stays crisp. The same string always gives the same face and colour, on the server and in the browser, so you can use an email or a user id and get a stable avatar for free.
Without color, the avatar picks one of six palette hues from the string. Pass a palette name or any CSS colour to set it yourself.
Examples
Group with a count
AvatarGroup overlaps its avatars, cuts a gap around each in the surface colour, and replaces the rest with a "+N" chip. Set outline to the colour behind the group so the gaps disappear into it.
Members with presence
A member list seeded by email, labelled by name. status adds a dot and appends the state to the accessible name, as in "Alan Turing, busy". The owner gets a ring; the bot gets a grey hatched face.
Fills
The same six names in every fill. Gradient draws half tone as a Bayer checker; dotted and hatched texture it instead; solid uses a lighter opacity. Full blocks are solid in every fill.
Props
Avatar
| Prop | Type | Default |
|---|---|---|
| name* The string the face and colour come from, such as a name, email or id. Also the accessible name. | Type: string | Default – |
| label Accessible name to use instead of name, when name is an id. | Type: string | Default – |
| size Diameter in pixels. Inside a group, the group's size. | Type: number | Default 40 |
| color A palette name or any CSS colour. Without it, one of six hues picked from name. | Type: DitherColor | Default – |
| fill How each block's tone is drawn. | Type: "gradient" | "dotted" | "hatched" | "solid" | Default "gradient" |
| shape A circle or a rounded square. | Type: "circle" | "square" | Default "circle" |
| ring A ring in the avatar's colour, for the current user or a selection. | Type: boolean | Default false |
| status A presence dot in the corner, added to the accessible name. | Type: "online" | "away" | "busy" | "offline" | Default – |
| bloom A soft glow in the avatar's colour. | Type: "off" | "low" | "high" | Default "off" |
AvatarGroup
| Prop | Type | Default |
|---|---|---|
| max Show at most this many avatars, then a +N chip. | Type: number | Default – |
| size Diameter of every avatar in pixels. | Type: number | Default 32 |
| outline Colour of the gap around each avatar. Match the surface behind the group. | Type: string | Default "var(--background)" |
| moreLabel What the +N chip announces to screen readers. | Type: (count: number) => string | Default "N more" |
avatarFace(name) and avatarColor(name) are exported too, if you want the same face elsewhere, such as an Open Graph image.
Notes
- The avatar is a
role="img"element named bylabelorname, plus the status when there is one. The group is arole="group"; give it anaria-labelsuch as "Viewers" when the context does not say. - The face is one or a few SVG paths, cached per name and fill. No canvas, no client JavaScript.
- Avatars in a group lift by 2 pixels on hover on devices that can hover. With reduced motion they stay put.