Avatar
Avatar shows a person as a photo, initials or a placeholder, with optional status badges. Use Avatar group and Compact avatar group to show who is in a space, a lesson or a thread.
Beta until design decides the status colours and adds a non-colour cue for Online, Busy and Away (C22, FC-15), and the Figma file is re-bound to local tokens (FC-10, FC-11, FC-14). Everything else on this page is implemented and tested.
Preview
Change the controls to try every supported combination. The preview renders the real component and the code updates with it.
import { Avatar } from '@nuton/ui'
<Avatar
size={56}
name="Emma Wright"
color="sky"
bottomStatus="online" bottomStatusLabel="Online"
/>Figma's persona photos, memoji and illustrations aren't shipped (provenance, D12). The portraits on this page are free Unsplash photos used only in these docs; the “solid image” ones have their backgrounds removed so the avatar colour shows behind them. Apps pass their own src.
Installation
Availability
@nuton/ui and @nuton/tokens are private workspace packages. They aren't published to a registry yet (D4: registry deferred; D12: distribution waits on the provenance review). Inside the Nuton monorepo, add them as workspace dependencies. The commands below apply once the packages are published.
1. Add the packages
pnpm add @nuton/ui @nuton/tokensnpm install @nuton/ui @nuton/tokensyarn add @nuton/ui @nuton/tokensPeer dependencies: react and react-dom 18 or later. The status icons come with @nuton/ui; add @remixicon/react only if you build a company logo from a Remix icon.
2. Load tokens and component styles once
// app/layout.tsx (or your app's entry file)
import '@nuton/tokens/tokens.css' // --nu-* design tokens
import '@nuton/ui/styles.css' // component styles (uses the tokens)Initials and counts use the Label and Paragraph type tokens, which read --nu-font-body (Inter). Map it to your loaded font as shown on the Button page. Avatar is a client component (it tracks image load errors), and you can render it from a server component without extra setup.
Usage
import { Avatar, AvatarGroup } from '@nuton/ui'
export function LessonAuthor({ author }: { author: { name: string; photoUrl?: string } }) {
return <Avatar size={32} name={author.name} src={author.photoUrl} color="purple" />
}
export function SpaceMembers({ students }: { students: { id: string; name: string }[] }) {
return (
<AvatarGroup size={32} max={4} aria-label="Students in this space">
{students.map((s) => <Avatar key={s.id} name={s.name} />)}
</AvatarGroup>
)
}- The default is 40px, gray. Content comes from the data:
srcshows the image,nameshows initials, and neither shows the placeholder. - Always pass
namewhen the avatar stands for a person. It names the avatar for screen readers and gives the initials if the image fails. - Pass a label with every status:
topStatusLabelfortopStatus,bottomStatusLabelforbottomStatus. - Give every group an
aria-labelthat says who the people are: “Students in Biology 101”. - Inside a group, the group's
sizewins; don't setsizeon the avatars.
Variants and sizes
Size, colour and status are props. The content kind (image, solid background, initials or placeholder) isn't a prop: it follows from the data you pass.
Sizes
807264564840322420<Avatar size={80} name="Arthur Taylor" color="blue" />
<Avatar size={72} name="Arthur Taylor" color="blue" />
<Avatar size={64} name="Arthur Taylor" color="blue" />
<Avatar size={56} name="Arthur Taylor" color="blue" />
<Avatar size={48} name="Arthur Taylor" color="blue" />
<Avatar size={40} name="Arthur Taylor" color="blue" /> // default
<Avatar size={32} name="Arthur Taylor" color="blue" />
<Avatar size={24} name="Arthur Taylor" color="blue" />
<Avatar size={20} name="Arthur Taylor" color="blue" />| Size | Badge box | Badge overhang | Initials type | Group overlap | Group “+N” | Group width (4 + “+N”) |
|---|---|---|---|---|---|---|
80 | 32 | 8 | Label X Large 24 | −16 | 24 | 336 |
72 | 32 | 8 | Label X Large 24 | −16 | 24 | 296 |
64 | 28 | 8 | Label X Large 24 | −16 | 24 | 256 |
56 | 24 | 6 | Label Large 18 | −16 | 24 | 216 |
48 | 20 | 6 | Label Large 18 | −12 | 20 | 192 |
40 | 18 | 6 | Label Medium 16 | −12 | 16 | 152 |
32 | 16 | 6 | Label Small 14 | −6 | 14 | 136 |
24 | 12 | 4 | Label X Small 12 | −4 | 12 | 104 |
20 | 10 | 4 | Label X Small 12, one initial (P2) | −4 | 11 | 84 |
Measured in the browser; all nine sizes and every group width match Figma exactly. Figma sets initials in remote Poppins styles; code uses the local Nuton Label styles at the same sizes (C22, FC-10).
Content

SW
<Avatar size={56} src="/samples/portrait-2.jpg" name="Arthur Taylor" />
<Avatar size={56} src="/samples/portrait-cutout-3.webp" name="Amara Okafor" color="yellow" solidBackground />
<Avatar size={56} name="Sophia Williams" color="yellow" />
<Avatar size={56} color="blue" />
<Avatar size={56} src="/samples/does-not-exist.png" name="Laura Perez" color="red" />Photos and cut-outs
A normal photo fills the circle. A cut-out (transparent background) with solidBackground sits on the colorpalette, like Figma's Solid BG avatars.








<Avatar size={56} src="/samples/portrait-1.jpg" name="Sophia Williams" />
<Avatar size={56} src="/samples/portrait-2.jpg" name="Arthur Taylor" />
<Avatar size={56} src="/samples/portrait-3.jpg" name="Emma Wright" />
<Avatar size={56} src="/samples/portrait-4.jpg" name="Juma Omondi" />
<Avatar size={56} src="/samples/portrait-5.jpg" name="Amara Okafor" />
<Avatar size={56} src="/samples/portrait-cutout-1.webp" name="Sophia Williams" color="purple" solidBackground />
<Avatar size={56} src="/samples/portrait-cutout-2.webp" name="Emma Wright" color="sky" solidBackground />
<Avatar size={56} src="/samples/portrait-cutout-3.webp" name="Amara Okafor" color="yellow" solidBackground />| You pass | You get | Figma content |
|---|---|---|
src | The image, cropped to a circle | Image |
src + solidBackground | A transparent image on the color palette | Solid BG · Memoji · Illustration |
name (or initials) | Initials on the color palette | Text |
| Neither | The placeholder person on the color palette | Icon |
src that fails to load | Initials, or the placeholder if there's no name | (code only) |
Initials
Initials are the first letter of the first and last word of name, uppercased. They're Unicode-aware, so accented and non-Latin names work. Override them with initials, or call getInitials yourself.
name | getInitials(name) |
|---|---|
'Juma Omondi' | 'JO' |
'Lena Müller' | 'LM' |
'Natalia Nowak-Kowalska' | 'NN' |
'Wei' | 'W' |
' ravi patel ' | 'RP' |
'Émile Zola' | 'ÉZ' |
Persona palettes
Figma's 12 personas use six colour pairs. Each is a Foundation /200 background with /950 text.
<Avatar size={48} name="James Brown" color="gray" />
<Avatar size={48} name="Sophia Williams" color="yellow" />
<Avatar size={48} name="Arthur Taylor" color="blue" />
<Avatar size={48} name="Emma Wright" color="sky" />
<Avatar size={48} name="Matthew Johnson" color="purple" />
<Avatar size={48} name="Laura Perez" color="red" />
<Avatar size={48} name="Wei Chen" color="gray" />
<Avatar size={48} name="Lena Müller" color="blue" />
<Avatar size={48} name="Juma Omondi" color="yellow" />
<Avatar size={48} name="Natalia Nowak" color="sky" />
<Avatar size={48} name="Ravi Patel" color="purple" />
<Avatar size={48} name="Nuray Aksoy" color="red" />color | Background | Initials | Solid BG image |
|---|---|---|---|
gray (default) | gray/200 | Text/Strong-950 | slate/200 (FC-11) |
yellow | yellow/200 | yellow/950 | yellow/200 |
blue | blue/200 | blue/950 | blue/200 |
sky | sky/200 | sky/950 | sky/200 |
purple | purple/200 | purple/950 | purple/200 |
red | red/200 | red/950 | red/200 |
Gray initials sit on gray/200, but Figma's gray “Solid BG” uses slate/200. Code follows Figma until design picks one (FC-11). Gray initials use Text/Strong-950, the same value as Figma's static black.
Top status
<Avatar size={56} name="Emma Wright" color="sky" topStatus="verified" topStatusLabel="Verified" />
<Avatar size={56} name="Emma Wright" color="sky" topStatus="pin" topStatusLabel="Pinned" />
<Avatar size={56} name="Emma Wright" color="sky" topStatus="favorite" topStatusLabel="Favourite" />
<Avatar size={56} name="Emma Wright" color="sky" topStatus="add" topStatusLabel="Add" />
<Avatar size={56} name="Emma Wright" color="sky" topStatus="remove" topStatusLabel="Remove" />
<Avatar size={56} name="Emma Wright" color="sky" topStatus="notification" topStatusLabel="New notification" />Bottom status
<Avatar size={56} name="Ravi Patel" color="purple" bottomStatus="online" bottomStatusLabel="Online" />
<Avatar size={56} name="Ravi Patel" color="purple" bottomStatus="offline" bottomStatusLabel="Offline" />
<Avatar size={56} name="Ravi Patel" color="purple" bottomStatus="busy" bottomStatusLabel="Busy" />
<Avatar size={56} name="Ravi Patel" color="purple" bottomStatus="away" bottomStatusLabel="Away" />
<Avatar
size={56} name="Ravi Patel" color="purple"
bottomStatus="company" bottomStatusLabel="Nuton staff"
companyLogo={<SpaceLogo />} // your own logo element
/>| Status | Colour token | Badge |
|---|---|---|
verified | State/Verified/Base | Verified badge icon on a white ring |
pin | State/Purple/Base | Pin icon on a filled disc |
favorite | State/Success/Base | Star icon on a filled disc |
add | State/Faded/Base | Plus icon on a filled disc |
remove | State/Error/Base | Close icon on a filled disc |
notification | State/Error/Base | Dot |
online | State/Success/Base | Dot |
offline | State/Faded/Base | Dot |
busy | State/Error/Base | Dot |
away | State/Pending/Base (nearest; no Nuton “away” colour) | Dot |
company | (your logo) | companyLogo on a white ring |
Gap: status colours and colour-only status (C22, FC-15)
Figma binds remote status colours that differ from Nuton's State tokens (verified, error and success), and its “away” colour has no Nuton token. Code uses the local tokens above until design decides. Online, Busy and Away differ only by colour (WCAG 1.4.1), so show the status as text wherever it matters. The API has no non-colour cue yet.
No brand assets ship in @nuton/ui (C15). Apps pass the logo through companyLogo; the purple building above is an example. Badges can be combined: one top and one bottom status on the same avatar.
Avatar group
Overlapping avatars, each with a 2px white ring, and a “+N” bubble on Weak-50 when there are more people. All nine sizes:
<AvatarGroup size={80} max={4} extra={5} aria-label="Space members">
<Avatar name="James Brown" color="gray" />
<Avatar name="Sophia Williams" color="yellow" />
<Avatar name="Arthur Taylor" color="blue" />
<Avatar name="Emma Wright" color="sky" />
</AvatarGroup>
// …and the same group at size 72, 64, 56, 48, 40, 32, 24 and 20“+N” counts the children beyond max plus extra (people you haven't rendered, for example because they aren't loaded). Here each group renders 4 avatars and adds extra={5}, so it shows +5.
Compact avatar group
Avatars and a “+N” count in a white pill. Two appearances, three sizes. By default it shows 3 avatars.
<CompactAvatarGroup size={40} extra={4} aria-label="Reviewers">
<Avatar name="James Brown" color="gray" />
<Avatar name="Sophia Williams" color="yellow" />
<Avatar name="Arthur Taylor" color="blue" />
</CompactAvatarGroup>
// size={32}, size={24}, and the same three with appearance="stroke"| Size | Padding (top/end/bottom/start) | Avatar overlap | Avatar–count gap | Count type |
|---|---|---|---|---|
40 | 2 / 12 / 2 / 2 | −2 | 10 | Paragraph Medium 16 |
32 | 2 / 10 / 2 / 2 | 8 | Paragraph Small 14 | |
24 | 2 / 8 / 2 / 2 | 6 | Paragraph X Small 12 |
appearance="default" has a shadow only; "stroke" adds a 1px Stroke/Soft-200 border. Measured 1–2px wider than Figma because the count is set in Inter rather than DM Sans (C22). Figma labels the Default variants Size=4 / 3 / 2 and the Stroke variants 40 / 32 / 24, and the frame holding the avatars is named “Buttons” (FC-14).
States and fallbacks
Avatar isn't interactive, so it has no hover, focus or disabled state. Its states are about content: what shows when an image loads, fails or is missing.

alt or name.
src and no name. Hidden from assistive tech unless it has a status label.Composition examples
Space members
Add and remove students: the group shows four and counts the rest. The count is also written out as text next to the group.
Presence with text
Where status matters, write it out. The badge is a quick cue; the text is what everyone can read (C22).
Here the screen reader hears the status twice (once in the avatar's name, once in the text). If that's too noisy, drop name and the status label from the avatar so it becomes decorative, and keep the visible text.
Avatar next to a visible name
When the person's name is already on screen, don't name the avatar again. Pass initials (or an image src) without name: with no name the avatar is hidden from assistive tech, and the visible name does the work.
Reviewers in a lesson card
Opening a profile
Avatar renders a <span> and isn't focusable. To open a profile, put the avatar and the visible name inside a link, and leave the avatar unnamed so the link isn't announced twice.
API reference
Avatar props
| Prop | Type | Default | Description |
|---|---|---|---|
size | 80 | 72 | 64 | 56 | 48 | 40 | 32 | 24 | 20 | 40 | Diameter in px. Inside a group, the group’s size wins. |
name | string | — | Person’s name. Gives the accessible name and the initials when there’s no image. |
src | string | — | Image URL (Figma Image, Memoji, Illustration). Falls back to initials, then the placeholder, if it fails to load. |
alt | string | — | Accessible name for the avatar. Defaults to name. |
initials | string | — | Overrides the initials worked out from name. |
color | 'gray' | 'yellow' | 'blue' | 'sky' | 'purple' | 'red' | 'gray' | Palette for initials, the placeholder and solidBackground images. |
solidBackground | boolean | false | Shows color behind a transparent image (Figma Solid BG, Memoji, Illustration). |
topStatus | 'verified' | 'pin' | 'favorite' | 'add' | 'remove' | 'notification' | — | Badge in the top-right corner. |
topStatusLabel | string | — | Text for topStatus, added to the accessible name. Needed for the status to be announced (development warning otherwise). |
bottomStatus | 'online' | 'offline' | 'busy' | 'away' | 'company' | — | Badge in the bottom-right corner. |
bottomStatusLabel | string | — | Text for bottomStatus, added to the accessible name. Needed for the status to be announced (development warning otherwise). |
companyLogo | ReactNode | — | Logo shown when bottomStatus="company". Apps supply it (C15). |
ref | Ref<HTMLSpanElement> | — | Forwarded to the root span. |
…rest | Omit<HTMLAttributes<HTMLSpanElement>, 'color' | 'children'> | — | Other span attributes (className, id, title, aria-*…). Avatar takes no children. |
AvatarGroup props
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | Avatar elements. Non-element children are ignored. |
size | 80 | 72 | 64 | 56 | 48 | 40 | 32 | 24 | 20 | 40 | Size of every avatar and the “+N” bubble. |
max | number | — | Show at most this many avatars. Unset shows them all. |
extra | number | 0 | People not rendered as children, added to the “+N” count. |
moreLabel | (count: number) => string | (n) => `${n} more` | Accessible text for “+N”. Pass a localised string. |
ref | Ref<HTMLDivElement> | — | Forwarded to the group div. |
…rest | HTMLAttributes<HTMLDivElement> | — | Other div attributes. Always pass aria-label. |
CompactAvatarGroup props
| Prop | Type | Default | Description |
|---|---|---|---|
childrenrequired | ReactNode | — | Avatar elements. Non-element children are ignored. |
size | 40 | 32 | 24 | 40 | Avatar size; the pill is 4px taller. |
appearance | 'default' | 'stroke' | 'default' | Shadow only, or shadow plus a 1px Stroke/Soft-200 border (Figma Style). |
max | number | 3 | Show at most this many avatars. |
extra | number | 0 | People not rendered as children, added to the “+N” count. |
moreLabel | (count: number) => string | (n) => `${n} more` | Screen-reader text for “+N”. Pass a localised string. |
ref | Ref<HTMLDivElement> | — | Forwarded to the group div. |
…rest | HTMLAttributes<HTMLDivElement> | — | Other div attributes. Always pass aria-label. |
getInitials
function getInitials(name: string): string
// First letter of the first and last word, uppercased with toLocaleUpperCase.
// Returns '' for an empty or blank name.Exported types: AvatarProps, AvatarSize, AvatarColor, AvatarTopStatus, AvatarBottomStatus, AvatarGroupProps, CompactAvatarGroupProps, CompactAvatarGroupSize.
Styling hooks
Avatar renders .nu-avatar with data-size, data-color, data-kind (image, text or icon), data-solid and data-in-group. Groups render .nu-avatar-group and .nu-avatar-compact with data-size (and data-appearance). Use props rather than styling these attributes. The component tokens below are the supported override points.
| Token | Set on | Controls |
|---|---|---|
--nu-avatar-size | Avatar, group | Diameter, from size |
--nu-avatar-bg / --nu-avatar-fg | Avatar | Palette background and initials colour, from color |
--nu-avatar-bg-solid | Avatar | Background behind solidBackground images |
--nu-avatar-badge / --nu-avatar-overhang | Avatar | Status badge box and how far it overhangs |
--nu-avatar-status | Status badge | Status colour |
--nu-avatar-group-overlap / --nu-avatar-group-more-size | Avatar group | Overlap and “+N” text size |
--nu-avatar-compact-pad-end / --nu-avatar-compact-gap | Compact group | End padding and avatar–count gap |
Accessibility
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Skips avatars and groups: they aren’t focusable. Put a link or button around an avatar that opens something. |
Screen readers
An avatar is one img named by alt or name, plus any status labels (“Emma Wright, Verified”). The inner image has empty alt text, so nothing is read twice. With no name and no status label it's hidden. Groups have role="group"; “+N” is read from moreLabel (default “5 more”).
Focus
Not focusable, so there's no focus indicator. A wrapping link or button provides the focus style.
Contrast and colour
Initials pass on every palette (7.02:1 to 15.04:1). Status discs against their white ring: Online and Favourite 2.85:1 and Away 2.10:1, below 3:1. Online, Busy and Away differ only by colour, so show status as text (C22, FC-15).
Touch and pointer
Avatars have no pointer behaviour. If you make one clickable, the link or button around it needs a target of at least 24 × 24 (WCAG 2.5.8), so don't use a bare 20px avatar as the target.
You must: pass name for every person; pass a label with every status; give every group an aria-label; show status as text where it matters.
Figma mapping
| Figma | Variants | Code |
|---|---|---|
| Avatar [1.1] (31:3191) | 12 personas × 9 sizes × Image · Solid BG · Memoji · Illustration · Text · Icon (648) | size 80–20; content follows the data (see Content); the 12 personas map to 6 color palettes |
| Top Status [1.1] (31:5998) | Verified · Pin · Favorite · Add · Remove · Notification | topStatus + topStatusLabel |
| Bottom Status [1.1] (31:6022) | Online · Offline · Busy · Away · Company | bottomStatus + bottomStatusLabel; Company uses companyLogo |
| Avatar Group [1.1] (31:6038) | 9 sizes; Edit Number; Show More | AvatarGroup size, max, extra; “+N” appears when there are more people |
| Compact Avatar Group [1.1] (31:6102) | Style Default · Stroke × 40 · 32 · 24 (FC-14) | CompactAvatarGroup appearance, size, max, extra |
Figma binds remote libraries (06-Foundations, 01-Tokens) and remote text styles (Poppins, DM Sans). Code uses the identical local Nuton Foundation colours, State tokens and Label and Paragraph styles; re-binding the Figma file is queued (C22, FC-10).
Guidelines and related components
Use Avatar for
- Showing who wrote, owns or is assigned to something: a lesson author, a space owner.
- Showing who is in a space, lesson or thread, with a group.
- A quick presence cue next to a name, backed by text.
Don't use Avatar for
- Spaces, courses or organisations. Avatar's placeholder is a person.
- Status on its own, without a name or text.
- A clickable control on its own. Put it inside a link together with the person's visible name.
Do and don't
Common mistakes
- Setting a status without its label, so screen readers never hear it (a development warning fires).
- Naming the avatar and showing the same name next to it, so screen readers read it twice.
- Leaving
moreLabelas the English default in a localised app. - Shipping Figma's persona photos or a brand logo inside the package (D12, C15). Apps pass their own images.