Resolved: light

Person

People show up everywhere in the portal — rosters, team lists, assignees, comment threads. This section covers the three building blocks: the DS Avatar (a circular identity chip with an image or initials fallback), AvatarGroup (a stacked, overflow-aware cluster of avatars), and the STD PersonCard — our own wrapper that composes an Avatar with a name, secondary line, labelled meta rows and badges. Flip the theme in the top bar to confirm every part re-skins via the semantic tokens; the avatar initials background, group overlap ring and card surface all resolve from CSS vars.

Avatar
@savethemarshalldogs/design-system — the atom. One person, one chip. Image when src is given, otherwise initials derived from name. Five sizes.
AvatarGroup
@savethemarshalldogs/design-system — a compact stack of Avatars with an overlap ring and a +N overflow chip. For showing many people in a small space (team membership, attendees).
PersonCard
@/components/ds (STD wrapper, NOT shipped by the DS) — a full person tile: avatar + name + secondary + meta rows + badges + optional aside, optionally rendered as a link.
which to use
Avatar for a bare identity chip, AvatarGroup when several people collapse into one control, PersonCard when a person needs a labelled, scannable record (with contact rows / status).

Avatar — sizes

Five fixed sizes (xs · sm · md · lg · xl), each a token-driven square that renders as a circle. Size scales the chip and its type together; it never changes color or shape. md is the default. Use the smallest size that stays legible in its context.

xs
sm
md
lg
xl
size — initials fallback, accent tint, across all five
size="xs"
--space-5 (20px) — dense rows, inline mentions, AvatarGroup stacks.
size="sm"
--space-6 (24px) — list rows, compact tables.
size="md"
--space-8 (32px) — the default; cards and standard rows.
size="lg"
--space-9 (40px) — profile headers, prominent person tiles.
size="xl"
--space-9 + --space-3 (≈52px) — page-level identity / hero blocks.

Avatar — image & initials fallback

Pass src and the avatar renders that image (object-fit: cover). Omit src — or when the image fails — and it falls back to initials derived from name (the DS initials() helper). Always pass name even with an image: it supplies the initials fallback and the accessible label.

src set → image
no src → “AL” initials
with src (image) vs no src (initials)
initials derive from one-, two- and three-part names
src
Image URL. When present, renders <img class=avatar__img> with object-fit: cover over the chip. Omit for an initials-only avatar.
name
Always pass it. Drives the initials fallback (via the DS initials() helper) and the accessible label, even when an image is shown.
fallback
No src (or an empty name) → initials on the neutral chip. An empty name renders an empty chip — pass a real name in production.

Avatar — accent, ring & square

Three boolean modifiers, all optional and combinable. accent tints the initials chip with the accent-soft fill (use for the current user or a highlighted person). ring adds an inset hairline so an avatar stays separated on a busy background. square swaps the pill radius for the md radius (logos / org marks rather than people).

default
accent
ring
square
default · accent · ring · square (size lg)
accent
boolean. Tints an initials avatar with --color-accent-soft / --color-accent. Reserve it to mark one person (e.g. the signed-in user). No effect on an image avatar's fill.
ring
boolean. Adds an inset hairline ring (--color-surface) so the avatar reads as separated on photos / colored panels and in AvatarGroup stacks.
square
boolean. Switches from the pill radius to --radius-md. Use for org/logo marks, not for people.

AvatarGroup — overflow +N

Stacks Avatars with a slight overlap and a surface-colored ring between them, then collapses everything past max into a +N chip. Use it where several people must read as one unit in a tight space — team membership, event attendees, an assignee list. Keep the children small (xs / sm) so the overlap stays tidy.

five children, no cap — all shown
+3
max={3} — the rest collapse into a +N chip
+96
max={4} with an explicit total override (+96)
max
Caps how many avatars render; the remainder fold into a single +N overflow chip. Omit max to show every child.
total
Overrides the +N count. Use it when you only render a few avatars but want the chip to reflect a larger real total (e.g. render 4, show +96).
children
Plain <Avatar> elements — keep them one small size (sm/xs). The group applies the negative margin + surface ring between them.
labelling
The +N chip is decorative; if the group is the only thing naming a set, add an accessible label/heading nearby so it's not just a visual count.

PersonCard — the STD wrapper

PersonCard is OURS (@/components/ds), not a DS export — it composes the DS Avatar + Text on a portal surface and adds the slots our app needs: a name, a secondary line, labelled meta rows, a badge cluster and a right-aligned aside. It is presentational and safe in a server or client tree. Use it whenever a person needs a scannable record rather than a bare chip.

Ada Lovelace

Foster coordinator · Brooklyn

minimal — name + secondary only
Grace Hopper

Transport volunteer

with an avatar image and a larger avatar
name (required)
The person's name — heading text AND the Avatar initials fallback (when name is a plain string). Pass nameText alongside if name is a node.
secondary
The line under the name — role, team, or headline. Rendered in DS Text size=small muted; truncates.
avatarSrc / avatarSize / avatarAccent
Forwarded to the DS Avatar. avatarSrc shows an image (initials otherwise); avatarSize defaults to md; avatarAccent tints the initials chip.

PersonCard — meta rows

Pass an array of { label, value } meta rows to turn the card into a labelled contact record. Each row is a small uppercase label paired with a value that truncates on overflow. Use it for email / phone / team / location — anything a reader scans down a column.

Ada Lovelace

Foster coordinator

Email
ada@savethedogs.io
Phone
(555) 010-2840
Team
Brooklyn fosters
Grace Hopper

Transport volunteer

Email
grace@savethedogs.io
Region
Hudson Valley
meta rows: email, phone, team
meta
PersonMetaRow[] — each { label, value, key? }. label is a small uppercase eyebrow; value truncates. Pass key when label is not a plain string.
value can be a node
Drop an <a> (mailto:/tel:) or a chip into value, not just text — it stays inside the truncating cell.

PersonCard — badges & aside

The badges slot takes a wrapping cluster of DS Badges / role pills (status, role, affinity flags). The aside slot is right-aligned for an action, a status chip or a chevron. Meaning is never carried by color alone — every badge keeps a text label.

Katherine Johnson

Lead organizer · NYC megaregion

Email
katherine@savethedogs.io
OrganizerVerifiedTransport
badges cluster (DS Badge tones)
Marie Curie

Pending volunteer

Joined
Jun 2026
Needs review
aside slot — a status badge on the right
badges
Any node — typically one or more DS <Badge> / role pills. Rendered as a wrapping gap-1.5 cluster under the meta rows.
aside
Right-aligned slot (margin-left:auto, vertically centered). Use for a status chip, a single action, or a chevron — keep it to one or two items.
never color alone
Every badge keeps a text label (Verified, Needs review…) — tone reinforces meaning, it does not replace the word.