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
  • Accessibility
  • Figma Avatar [1.1] 31:3191
  • packages/ui/src/components/Avatar

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.

EW
tsx
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

bash
pnpm add @nuton/ui @nuton/tokens

Peer 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

tsx
// 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

tsx
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: src shows the image, name shows initials, and neither shows the placeholder.
  • Always pass name when 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: topStatusLabel for topStatus, bottomStatusLabel for bottomStatus.
  • Give every group an aria-label that says who the people are: “Students in Biology 101”.
  • Inside a group, the group's size wins; don't set size on 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

AT
80
AT
72
AT
64
AT
56
AT
48
AT
40
AT
32
AT
24
AT
20
SizeBadge boxBadge overhangInitials typeGroup overlapGroup “+N”Group width (4 + “+N”)
80328Label X Large 24−1624336
72328Label X Large 24−1624296
64288Label X Large 24−1624256
56246Label Large 18−1624216
48206Label Large 18−1220192
40186Label Medium 16−1216152
32166Label Small 14−614136
24124Label X Small 12−412104
20104Label X Small 12, one initial (P2)−41184

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

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.

You passYou getFigma content
srcThe image, cropped to a circleImage
src + solidBackgroundA transparent image on the color paletteSolid BG · Memoji · Illustration
name (or initials)Initials on the color paletteText
NeitherThe placeholder person on the color paletteIcon
src that fails to loadInitials, 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.

namegetInitials(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.

JBSWATEWMJLPWCLMJONNRPNA
colorBackgroundInitialsSolid BG image
gray (default)gray/200Text/Strong-950slate/200 (FC-11)
yellowyellow/200yellow/950yellow/200
blueblue/200blue/950blue/200
skysky/200sky/950sky/200
purplepurple/200purple/950purple/200
redred/200red/950red/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

EWEWEWEWEWEW

Bottom status

RPRPRPRPRP
StatusColour tokenBadge
verifiedState/Verified/BaseVerified badge icon on a white ring
pinState/Purple/BasePin icon on a filled disc
favoriteState/Success/BaseStar icon on a filled disc
addState/Faded/BasePlus icon on a filled disc
removeState/Error/BaseClose icon on a filled disc
notificationState/Error/BaseDot
onlineState/Success/BaseDot
offlineState/Faded/BaseDot
busyState/Error/BaseDot
awayState/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:

JBSWATEW+5
JBSWATEW+5
JBSWATEW+5
JBSWATEW+5
JBSWATEW+5
JBSWATEW+5
JBSWATEW+5
JBSWATEW+5
JBSWATEW+5

“+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.

JBSWAT4 more
JBSWAT4 more
JBSWAT4 more
JBSWAT4 more
JBSWAT4 more
JBSWAT4 more
SizePadding (top/end/bottom/start)Avatar overlapAvatar–count gapCount type
402 / 12 / 2 / 2−210Paragraph Medium 16
322 / 10 / 2 / 28Paragraph Small 14
242 / 8 / 2 / 26Paragraph 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.

ImageThe image loaded. It's named by alt or name.
Image failedFalls back to initials, including images that fail before the page hydrates.
Image failed, no nameFalls back to the placeholder. With no name it's decorative and hidden.
PlaceholderNo src and no name. Hidden from assistive tech unless it has a status label.
JO
With statusRead as “Juma Omondi, New notification, Online”.
No loading style
LoadingThere's no skeleton or loading state; the placeholder or initials show until the image arrives.

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.

WCLMJONN+2

6 students in Biology 101

Presence with text

Where status matters, write it out. The badge is a quick cue; the text is what everyone can read (C22).

  • LMLena MüllerOnline
  • RPRavi PatelBusy, teaching until 3 pm
  • NNNatalia NowakAway

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.

Lesson by Juma Omondi

Reviewers in a lesson card

WCNAMJ2 more

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

Avatar props
PropTypeDefaultDescription
size80 | 72 | 64 | 56 | 48 | 40 | 32 | 24 | 2040Diameter in px. Inside a group, the group’s size wins.
namestring—Person’s name. Gives the accessible name and the initials when there’s no image.
srcstring—Image URL (Figma Image, Memoji, Illustration). Falls back to initials, then the placeholder, if it fails to load.
altstring—Accessible name for the avatar. Defaults to name.
initialsstring—Overrides the initials worked out from name.
color'gray' | 'yellow' | 'blue' | 'sky' | 'purple' | 'red''gray'Palette for initials, the placeholder and solidBackground images.
solidBackgroundbooleanfalseShows color behind a transparent image (Figma Solid BG, Memoji, Illustration).
topStatus'verified' | 'pin' | 'favorite' | 'add' | 'remove' | 'notification'—Badge in the top-right corner.
topStatusLabelstring—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.
bottomStatusLabelstring—Text for bottomStatus, added to the accessible name. Needed for the status to be announced (development warning otherwise).
companyLogoReactNode—Logo shown when bottomStatus="company". Apps supply it (C15).
refRef<HTMLSpanElement>—Forwarded to the root span.
…restOmit<HTMLAttributes<HTMLSpanElement>, 'color' | 'children'>—Other span attributes (className, id, title, aria-*…). Avatar takes no children.

AvatarGroup props

AvatarGroup props
PropTypeDefaultDescription
childrenrequiredReactNode—Avatar elements. Non-element children are ignored.
size80 | 72 | 64 | 56 | 48 | 40 | 32 | 24 | 2040Size of every avatar and the “+N” bubble.
maxnumber—Show at most this many avatars. Unset shows them all.
extranumber0People not rendered as children, added to the “+N” count.
moreLabel(count: number) => string(n) => `${n} more`Accessible text for “+N”. Pass a localised string.
refRef<HTMLDivElement>—Forwarded to the group div.
…restHTMLAttributes<HTMLDivElement>—Other div attributes. Always pass aria-label.

CompactAvatarGroup props

CompactAvatarGroup props
PropTypeDefaultDescription
childrenrequiredReactNode—Avatar elements. Non-element children are ignored.
size40 | 32 | 2440Avatar size; the pill is 4px taller.
appearance'default' | 'stroke''default'Shadow only, or shadow plus a 1px Stroke/Soft-200 border (Figma Style).
maxnumber3Show at most this many avatars.
extranumber0People 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.
refRef<HTMLDivElement>—Forwarded to the group div.
…restHTMLAttributes<HTMLDivElement>—Other div attributes. Always pass aria-label.

getInitials

tsx
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.

TokenSet onControls
--nu-avatar-sizeAvatar, groupDiameter, from size
--nu-avatar-bg / --nu-avatar-fgAvatarPalette background and initials colour, from color
--nu-avatar-bg-solidAvatarBackground behind solidBackground images
--nu-avatar-badge / --nu-avatar-overhangAvatarStatus badge box and how far it overhangs
--nu-avatar-statusStatus badgeStatus colour
--nu-avatar-group-overlap / --nu-avatar-group-more-sizeAvatar groupOverlap and “+N” text size
--nu-avatar-compact-pad-end / --nu-avatar-compact-gapCompact groupEnd padding and avatar–count gap

Accessibility

Keyboard

KeyBehaviour
TabSkips 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

FigmaVariantsCode
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 · NotificationtopStatus + topStatusLabel
Bottom Status [1.1] (31:6022)Online · Offline · Busy · Away · CompanybottomStatus + bottomStatusLabel; Company uses companyLogo
Avatar Group [1.1] (31:6038)9 sizes; Edit Number; Show MoreAvatarGroup 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

RPRavi Patel · Busy
DoWrite the status out where it matters. Online, Busy and Away look alike apart from colour.
RPRavi Patel
Don'tDon't rely on the dot alone. People who can't tell the colours apart can't tell the statuses apart.
JBSWATEW+8
DoCap a group and count the rest. Four faces and “+8” read at a glance and keep the row short.
JBSWATEWMJLPWCLMJONNRPNA
Don'tDon't show everyone. A long overlapping row is hard to scan and overflows on small screens.
EWMJLP3 more
DoUse one size within a group. Set size on the group; it applies to every avatar.
EWMJLP
Don'tDon't mix avatar sizes in a row. Different sizes suggest a hierarchy that isn't there.

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 moreLabel as 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.