# Avatar

> Square user badge. Image by default, two-letter initials fallback, optional status dot. Three sizes locked to the type scale.

- Category: primitive
- Status: stable (since 0.3.0)
- A11y pattern: https://www.w3.org/WAI/ARIA/apg/
- Tokens: --background-tertiary, --foreground-primary
- Playground: https://design.freecodecamp.org/playground#avatar
- npm dependencies: `react@>=18 <20`
- Registry dependencies: [theme](https://design.freecodecamp.org/registry/theme.md)
- Files:
  - `Avatar.tsx` → `src/ui/avatar/Avatar.tsx` (raw: https://design.freecodecamp.org/registry/avatar/Avatar.tsx)
  - `avatar.css` → `src/ui/avatar/avatar.css` (raw: https://design.freecodecamp.org/registry/avatar/avatar.css)

## Install (copy source)

1. Ensure the theme is installed once per project - tokens.css + base.css imported globally, fonts available. See https://design.freecodecamp.org/registry/theme.md and https://design.freecodecamp.org/registry/starter.md.
2. Copy the files below into `src/ui/avatar/` (adjust to your project layout) and import the CSS once from your global stylesheet, e.g. `@import './ui/avatar/avatar.css';`.
3. Colors, spacing and type come from tokens - tailor the component by editing the copied source; recolour by editing tokens.css, not the component CSS.

## Usage

Square, flat, high-contrast - the Command-line Chic avatar. Fall back
to initials when no image is available (the 99% case in new sign-ups)
and attach a status dot when presence matters.

## Accessibility

When `src` is present, `alt` is filled from `name`. When falling back
to initials, the wrapper carries `aria-label={name}` and the initials
node is `aria-hidden`. Status dots include an `aria-label`
describing the state.

## Example

```tsx
import { Avatar } from './ui/avatar/Avatar';

<Avatar size="sm" initials="RW" />
<Avatar size="md" initials="QC" status="online" />
<Avatar size="lg" src="/u/quincy.jpg" alt="Quincy Larson" status="away" />
```

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `name` | `string` | yes | - |  |
| `src` | `string` | no | - |  |
| `size` | `enum` | no | `md` |  |
| `status` | `enum` | no | - |  |

## Source: Avatar.tsx

```tsx
import React, { forwardRef } from 'react';

export type AvatarSize = 'sm' | 'md' | 'lg';
export type AvatarStatus = 'online' | 'away' | 'offline';

export interface AvatarProps extends React.HTMLAttributes<HTMLSpanElement> {
  name: string;
  src?: string;
  size?: AvatarSize;
  status?: AvatarStatus;
}

function initialsFor(name: string): string {
  const parts = name.trim().split(/\s+/).filter(Boolean);
  if (parts.length === 0) return '?';
  if (parts.length === 1) return (parts[0]?.[0] ?? '?').toUpperCase();
  const first = parts[0]?.[0] ?? '';
  const last = parts[parts.length - 1]?.[0] ?? '';
  return `${first}${last}`.toUpperCase();
}

export const Avatar = forwardRef<HTMLSpanElement, AvatarProps>(
  ({ name, src, size = 'md', status, className = '', ...rest }, ref) => {
    const classes = ['avatar', `avatar--${size}`, className]
      .filter(Boolean)
      .join(' ');
    return (
      <span
        ref={ref}
        className={classes}
        aria-label={src ? undefined : name}
        {...rest}
      >
        {src ? (
          <img src={src} alt={name} className='avatar__img' />
        ) : (
          <span aria-hidden='true' className='avatar__initials'>
            {initialsFor(name)}
          </span>
        )}
        {status && (
          <span
            className={`avatar__status avatar__status--${status}`}
            aria-label={`status: ${status}`}
          />
        )}
      </span>
    );
  }
);
Avatar.displayName = 'Avatar';
```

## Source: avatar.css

```css
.avatar {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--background-tertiary);
  color: var(--foreground-primary);
  font-family: var(--font-sans, 'Lato', system-ui, sans-serif);
  font-weight: 700;
  overflow: hidden;
  border: 2px solid var(--background-tertiary);
}
.avatar--sm {
  width: 1.75rem;
  height: 1.75rem;
  font-size: 0.75rem;
}
.avatar--md {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 0.95rem;
}
.avatar--lg {
  width: 3.5rem;
  height: 3.5rem;
  font-size: 1.25rem;
}
.avatar__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.avatar__initials {
  letter-spacing: 0.02em;
}
.avatar__status {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 0.6em;
  height: 0.6em;
  border: 2px solid var(--background-primary);
}
.avatar__status--online {
  background: var(--success-color, #55b848);
}
.avatar__status--away {
  background: var(--warning-color, #f1be32);
}
.avatar__status--offline {
  background: var(--background-tertiary);
}
```

## HTML / vanilla variant

```html
<span class="avatar avatar--md">
  <span class="avatar__initials">QC</span>
  <span class="avatar__status avatar__status--online"></span>
</span>
```

Interactive behaviours for plain HTML come from the vanilla runtime (data-uikit-* attributes): https://design.freecodecamp.org/registry/vanilla.md - or download https://design.freecodecamp.org/cdn/uikit.global.js once and self-host it (do not hotlink).

## For coding agents

This library is distributed as copyable source, not an npm package. Start at https://design.freecodecamp.org/registry/starter.md, discover components via https://design.freecodecamp.org/llms.txt, and copy files into the consuming project. Keep token names intact; recolour by editing the copied tokens.css.
