FairGarden Design

Avatar

A Base UI Avatar for a person or organization beside a byline, comment, member list or account menu: a circular photo, seal or initials face inside a ring.

import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
  AvatarStatus,
} from '@fairgarden-private/design/components/Avatar'

Give the image alt text with the name, or empty alt when the name is beside it. AvatarFallback is the initials face: a nested light face with one or two caps initials, shown until the photo loads or if it fails; never color-code it. AvatarStatus puts a status glyph on its own disc at the frame's bottom end; show the state in words too, since presence is never a color dot alone. As a link or menu trigger, a ring up to 40 px steps to --primary12 at the same weight on hover; from 64 px it does not change. Photos print in grayscale inside a 0.75 pt ring.

Sizes, initials, status and groups

Sizes, initials, status and groups

Sizes are xs 24, sm 32, md 40 (default), lg 64 and xl 128 px, fixed at every width. Up to 40 px the ring is a --role-rule hairline; from 64 px it is --border-size-2 in the text ink. AvatarGroup overlaps its avatars by a quarter, separates them with a halo ring and ends with a "+N" avatar: three avatars at base, up to five from 1024 px.

ADADADADAD
BOBOBOBOBO
ADAna Díaz · OnlineBOBen Okafor · Away
ADBOCPDREM
AvatarVariants.tsx
import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
  AvatarStatus,
} from '@fairgarden-private/design/components/Avatar'
import { portrait } from '../portrait'
import styles from './variants.module.css'

const sizes = ['xs', 'sm', 'md', 'lg', 'xl'] as const
const people = ['Ana Díaz', 'Ben Okafor', 'Chloe Park', 'Dev Rao', 'Eli Moss', 'Fay Lund']

function initials(name: string) {
  return name
    .split(' ')
    .map((part) => part[0])
    .join('')
}

/** Five fixed sizes; the initials face shows until the photo loads, or if it fails. */
export function AvatarVariants() {
  return (
    <div className={styles.stack}>
      <div className={styles.row}>
        {sizes.map((size) => (
          <Avatar key={size} size={size}>
            <AvatarImage src={portrait} alt="Ana Díaz" />
            <AvatarFallback>AD</AvatarFallback>
          </Avatar>
        ))}
      </div>
      <div className={styles.row}>
        {sizes.map((size) => (
          <Avatar key={size} size={size}>
            <AvatarFallback>BO</AvatarFallback>
          </Avatar>
        ))}
      </div>
      <div className={styles.row}>
        <Avatar size="lg">
          <AvatarImage src={portrait} alt="" />
          <AvatarFallback>AD</AvatarFallback>
          <AvatarStatus status="success" label="Online" />
        </Avatar>
        <span className={styles.copy}>Ana Díaz · Online</span>
        <Avatar size="lg">
          <AvatarFallback>BO</AvatarFallback>
          <AvatarStatus status="warning" label="Away" />
        </Avatar>
        <span className={styles.copy}>Ben Okafor · Away</span>
      </div>
      <AvatarGroup size="md" total={9} aria-label="Stewardship team">
        {people.map((name) => (
          <Avatar key={name}>
            <AvatarFallback>{initials(name)}</AvatarFallback>
          </Avatar>
        ))}
      </AvatarGroup>
    </div>
  )
}

Primary scale

Primary scale

primary drives the ring. The initials face and status disc are their own light faces, so their inks never change.

ADADBO
AvatarColor.tsx
import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
  AvatarStatus,
} from '@fairgarden-private/design/components/Avatar'
import { portrait } from '../portrait'
import styles from './color.module.css'

/** `primary` drives the ring; the initials face and status disc keep their own light face. */
export function AvatarColor() {
  return (
    <div className={styles.row}>
      <Avatar size="md" primary="plum">
        <AvatarImage src={portrait} alt="Ana Díaz" />
        <AvatarFallback>AD</AvatarFallback>
      </Avatar>
      <Avatar size="lg" primary="plum">
        <AvatarImage src={portrait} alt="Ana Díaz" />
        <AvatarFallback>AD</AvatarFallback>
      </Avatar>
      <Avatar size="lg" primary="bronze">
        <AvatarFallback>BO</AvatarFallback>
        <AvatarStatus status="info" label="In a meeting" />
      </Avatar>
    </div>
  )
}

On grounds

On paper and forest

paper

ADBO
ADBOCPDR

forest

ADBO
ADBOCPDR
AvatarGrounds.tsx
import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  AvatarImage,
  AvatarStatus,
} from '@fairgarden-private/design/components/Avatar'
import { PresetGround } from '@/components/PresetGround'
import { portrait } from '../portrait'
import styles from './grounds.module.css'

const presets = ['paper', 'forest'] as const

/** Rings follow the ground; the initials face stays a light face; the group ring cuts a ground-colored gap. */
export function AvatarGrounds() {
  return (
    <div className={styles.row}>
      {presets.map((preset) => (
        <PresetGround key={preset} preset={preset} className={styles.face}>
          <p className={styles.name}>{preset}</p>
          <div className={styles.avatars}>
            <Avatar size="md">
              <AvatarImage src={portrait} alt="Ana Díaz" />
              <AvatarFallback>AD</AvatarFallback>
            </Avatar>
            <Avatar size="lg">
              <AvatarFallback>BO</AvatarFallback>
              <AvatarStatus status="success" label="Online" />
            </Avatar>
          </div>
          <AvatarGroup size="sm" aria-label="Volunteers">
            <Avatar>
              <AvatarImage src={portrait} alt="Ana Díaz" />
              <AvatarFallback>AD</AvatarFallback>
            </Avatar>
            <Avatar>
              <AvatarFallback>BO</AvatarFallback>
            </Avatar>
            <Avatar>
              <AvatarFallback>CP</AvatarFallback>
            </Avatar>
            <Avatar>
              <AvatarFallback>DR</AvatarFallback>
            </Avatar>
          </AvatarGroup>
        </PresetGround>
      ))}
    </div>
  )
}

API Reference

Avatar

The circular frame and its ring. Compose AvatarImage (a photo or a seal), AvatarFallback (1–2 initials, shown until the image loads or if it fails) and an optional AvatarStatus. Give the image alt text with the name, or empty alt when the name is adjacent. As a link or menu trigger, wrap it (or pass render): on hover a ring up to 40 px steps to --primary12 at the same weight.

PropTypeDescription
primary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: the ring. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale, accepted for the shared contract; no part uses it.

size
'xs' | 'sm' | 'md' | 'lg' | 'xl' | null | undefined

xs 24, sm 32, md 40 (default), lg 64, xl 128 px. Up to 40 px the ring is --border-size-1 --role-rule; from 64 px it is --border-size-2 --primary12. Sizes are fixed at every width.

AvatarImage and AvatarFallback

The photo or seal, cropped to the circle. Prints in grayscale.

The initials face: a nested white face (--primary1) with 1–2 caps initials in the UI sans at weight 700 and --primary12, the same on every ground. Never color-code initials backgrounds.

AvatarStatus

The status glyph (§1.5.4, with its inner mark) on its own white disc at the frame’s bottom end, --size-px-3, in --role-status of its status scale.

PropTypeDescription
label
string | undefined

The state in words, e.g. “Away”: the glyph’s accessible name. Show the word beside the avatar too; presence is never a color dot alone.

status
Status | undefined

The presence or state: info, success, warning or danger (§1.5.4).

AvatarGroup

Overlapping avatars (−25%) separated by a --size-px-1 ring in --role-halo, ending in a “+N” initials avatar: 3 avatars at base, up to 5 from 1024 px.

PropTypeDescription
moreLabel
((count: number) => string) | undefined

Accessible name for the “+N” avatar; receives N. Default “N more”.

primary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale for the group’s avatars. Never defaulted.

secondary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale, accepted for the shared contract; no part uses it.

size
'xs' | 'sm' | 'md' | 'lg' | 'xl' | null | undefined

Size of every avatar in the group. Default md.

total
number | undefined

How many people the group stands for, when more exist than the avatars passed. Default: the number of avatars.

Additional types

avatar
type avatar = avatar
AvatarFallbackProps

Props for AvatarFallback: Base UI Avatar Fallback props.

type AvatarFallbackProps = AvatarFallback.AvatarFallbackProps
avatarGroup

The group’s classes: its size publishes the overlap.

type avatarGroup = avatarGroup
AvatarGroupProps

Props for AvatarGroup: div props, render, the size, the total and the color axes.

type AvatarGroupProps = React.ClassAttributes<HTMLDivElement> &
  React.HTMLAttributes<HTMLDivElement> &
  __type & {
    size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | null;
    total?: number;
    primary?:
      | 'olive'
      | 'sage'
      | 'slate'
      | 'sand'
      | 'gray'
      | 'mauve'
      | 'brown'
      | 'bronze'
      | 'gold'
      | 'red'
      | 'ruby'
      | 'crimson'
      | 'tomato'
      | 'pink'
      | 'plum'
      | 'indigo'
      | 'iris'
      | 'violet'
      | 'purple'
      | null;
    secondary?:
      | 'olive'
      | 'sage'
      | 'slate'
      | 'sand'
      | 'gray'
      | 'mauve'
      | 'brown'
      | 'bronze'
      | 'gold'
      | 'red'
      | 'ruby'
      | 'crimson'
      | 'tomato'
      | 'pink'
      | 'plum'
      | 'indigo'
      | 'iris'
      | 'violet'
      | 'purple'
      | 'amber'
      | 'blue'
      | 'cyan'
      | 'grass'
      | 'green'
      | 'jade'
      | 'lime'
      | 'mint'
      | 'orange'
      | 'sky'
      | 'teal'
      | 'yellow'
      | null;
    moreLabel?: (count: number) => string;
  }
AvatarImageProps

Props for AvatarImage: Base UI Avatar Image props.

type AvatarImageProps = AvatarImage.AvatarImageProps
AvatarProps

Props for Avatar: Base UI Avatar Root props plus the size and color axes.

type AvatarProps = AvatarRootProps & {
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | null;
  primary?:
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'ruby'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  secondary?:
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'ruby'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
}
AvatarSize
type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'<'xs' | 'sm' | 'md' | 'lg' | 'xl' | null | undefined>
avatarStatus

The status disc’s class.

type avatarStatus = avatarStatus
AvatarStatusProps

Props for AvatarStatus: span props, the status and its word.

type AvatarStatusProps = {
  /** The presence or state: info, success, warning or danger (§1.5.4). */
  status: 'info' | 'success' | 'warning' | 'danger';
  /**
   * The state in words, e.g. "Away": the glyph's accessible name. Show the
   * word beside the avatar too; presence is never a color dot alone.
   */
  label?: string;
}

Specification: DESIGN-SYSTEM.md §10.12 (avatar) and §1.5.4 (status).