Avatar
StableInitials on a softened grey square, in five sizes, and a stack that folds extra people into a count.
The brand square with the corners eased, carrying one or two initials. Its grey is worked out from the name, so the same person always gets the same one of five tones, in both themes. Next to a written name it stays silent; on its own it can be an image named after the person.
Updated View source
Sizes and tones
import { Avatar } from '@rchursin/product/ui'
const sizes = [ ['xs', 16], ['sm', 20], ['md', 24], ['lg', 32], ['xl', 40],] as const
/** Five names that land on the five greys */const team = ['Alex Rivera', 'Jonas Weber', 'Theo Park', 'Hana Sato', 'Mara Quinn']
/** Initials on a softened square. The grey is worked out from the name, so a person always gets the same one. */export default function AvatarSizes() { return ( <div className="flex flex-col items-center gap-10"> <ul className="flex items-end gap-5"> {sizes.map(([size, px]) => ( <li key={size} className="flex flex-col items-center gap-2"> <Avatar name="Mara Quinn" size={size} /> <span className="font-mono text-[11px] text-fg-3 tabular-nums"> {size} {px} </span> </li> ))} </ul> <ul className="flex flex-wrap justify-center gap-x-5 gap-y-3"> {team.map((name) => ( <li key={name} className="flex items-center gap-2 text-[13px] text-fg"> <Avatar name={name} size="md" /> {name} </li> ))} </ul> </div> )}Usage
import { Avatar, AvatarStack } from '@rchursin/product/ui'<Avatar name="Mara Quinn" /><AvatarStack people={[{ name: 'Mara Quinn' }, { name: 'Theo Park' }]} />Examples
Avatar stack
Up to four faces by default, then “+3”. Each face has a ring in the page colour so the overlap stays crisp.
import { AvatarStack } from '@rchursin/product/ui'
const reviewers = [ { name: 'Mara Quinn' }, { name: 'Theo Park' }, { name: 'Ines Varga' }, { name: 'Sam Okafor' }, { name: 'Alex Rivera' }, { name: 'Hana Sato' }, { name: 'Jonas Weber' },]
/** Up to `max` faces, then a count. Screen readers hear one image named with every person. */export default function AvatarStackExample() { return ( <dl className="grid grid-cols-[auto_auto] items-center gap-x-6 gap-y-4 text-[13px]"> <dt className="text-fg-3">Reviewers</dt> <dd> <AvatarStack people={reviewers} /> </dd> <dt className="text-fg-3">Standup</dt> <dd> <AvatarStack people={reviewers.slice(0, 3)} size="md" /> </dd> <dt className="text-fg-3">Pairing</dt> <dd> <AvatarStack people={[{ name: 'Sam Okafor' }, { name: 'Alex Rivera', initials: 'AR' }]} size="lg" /> </dd> </dl> )}Named or silent
Silent beside a name; named with label when it stands alone. initials fixes names whose first two words are the wrong letters.
import { Avatar } from '@rchursin/product/ui'
/** * Next to a name, the avatar is decoration and stays silent. On its own, `label` makes it an * image named after the person. `initials` fixes names whose first two words aren't the right letters. */export default function AvatarLabelled() { return ( <ul className="flex flex-col gap-4 text-[13px]"> <li className="flex items-center gap-2.5"> <Avatar name="Theo Park" size="lg" /> <span className="flex flex-col"> <span className="font-medium text-fg">Theo Park</span> <span className="text-fg-3">Reviewed RC-125</span> </span> </li> <li className="flex items-center gap-2.5"> <span className="text-fg-3">Owner</span> <Avatar name="Ines Varga" label /> </li> <li className="flex items-center gap-2.5"> <Avatar name="Ana de la Cruz" initials="AC" size="lg" /> <span className="flex flex-col"> <span className="font-medium text-fg">Ana de la Cruz</span> <span className="text-fg-3">“AC”, not “AD”</span> </span> </li> </ul> )}API
Avatar props
| Prop | Type | Default | Description |
|---|---|---|---|
namerequired | string | – | The person’s name. Picks the grey, gives the initials (first letter of the first two words) and, with label, the accessible name. |
initials | string | – | Letters to show instead of the ones taken from the name. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | A square of 16, 20, 24, 32 or 40 pixels; the corner radius and type size scale with it. |
label | boolean | false | Make it an image named after the person (role="img"). Leave off when the name is written next to it. |
className | string | – | Extra classes on the square. |
AvatarStack props
| Prop | Type | Default | Description |
|---|---|---|---|
peoplerequired | { name: string; initials?: string }[] | – | Everyone in the group, in the order to show them. Names must be unique (they are the React keys). |
max | number | 4 | How many faces to draw before the rest become “+N”. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'sm' | Size of each face. |
toneFor(key)
| Prop | Type | Default | Description |
|---|---|---|---|
keyrequired | string | – | Any stable string, usually the name. Returns one of five background-and-text class pairs (light and dark), the same every time for the same key. |
Accessibility
- By default the square is aria-hidden: the name written next to it already says who it is, and screen readers don’t hear the initials twice.
- With label it becomes role="img" with the full name as its aria-label, for avatars that stand alone in a row or a cell.
- AvatarStack is one image named with every person, including the ones folded into “+N”: “Mara Quinn, Theo Park, Ines Varga, …”.
- All five tones keep the initials at 4.5:1 or more against their square, in both themes.
Guidelines
Do
- Write the name next to the avatar wherever there is room.
- Pass label when an avatar stands alone, so it has a name.
- Use initials for names whose first two words aren’t the right letters.
Don’t
- Don’t rely on the grey to tell people apart; it is a hint, and only five greys exist.
- Don’t use a stack where people must be picked one by one; it is a single image.
In use
Related
- Status & priorityTask glyphs told apart by shape: a square that fills as work moves on, priority bars, assignee, labels and estimate.
- ChipA small label on a thing, in four shapes (outline, solid, muted, dashed), with an optional icon and remove button.
- Agenda listEvents grouped by day under headings that can stick, earliest first, each with its time, calendar pattern and people.
Source
The component’s own files, exactly as they run on this site.
/** * Small pieces: avatars, chips, tooltip, empty state. */import { cloneElement, isValidElement, useEffect, useId, useRef, useState, type ReactElement, type ReactNode,} from "react"import { cx } from "../../components/cx"import { Icon, type IconName } from "../icons/Icon"import { computePosition } from "./popover"
/* ---------- Avatar: initials on a grey square (the brand square, softened) ---------- */
const tones = [