# Avatar

Shows a person or an organisation as a round picture, or as their initials when there is no picture.

- **Import:** `import { Avatar, AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup, AvatarGroupCount } from "@booleanpress/ui/avatar"`
- **Radix Avatar:** <https://www.radix-ui.com/primitives/docs/components/avatar>
- **Page:** <https://ui.booleanpress.com/components/avatar> · @booleanpress/ui 0.1.0

## Usage

Put an `AvatarImage` and an `AvatarFallback` inside `Avatar`. The image is requested first; the fallback shows until it has loaded, and stays if it fails or if there is no `src`.

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@booleanpress/ui/avatar"

export function Person() {
  return (
    <Avatar>
      <AvatarImage src="/people/ada.png" alt="Ada Lovelace" />
      <AvatarFallback>AL</AvatarFallback>
    </Avatar>
  )
}
```

`size` is `sm` (24 px), `default` (32 px) or `lg` (40 px). `AvatarBadge` adds a small dot or icon at the bottom corner at the inline end (for presence); `AvatarGroup` overlaps avatars in a row, and `AvatarGroupCount` is the "+4" chip at its end.

The fallback is the consumer's: the component does not derive initials. Use `AvatarFallback`'s `delayMs` to hold it back for a moment, so fast images do not flash it.

## Examples

### Image

A picture, with the initials as the fallback. This one is an inline SVG, so it needs no network.

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@booleanpress/ui/avatar"

const PHOTO =
  "data:image/svg+xml;utf8," +
  encodeURIComponent(
    '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" fill="#6366f1"/><circle cx="32" cy="25" r="11" fill="#e0e7ff"/><path d="M10 64c2-16 12-24 22-24s20 8 22 24z" fill="#e0e7ff"/></svg>'
  )

export default function AvatarImageExample() {
  return (
    <Avatar>
      <AvatarImage src={PHOTO} alt="Ada Lovelace" />
      <AvatarFallback>AL</AvatarFallback>
    </Avatar>
  )
}
```

### Fallback

Initials with no image, and initials under an image that has no source.

```tsx
import { Avatar, AvatarFallback, AvatarImage } from "@booleanpress/ui/avatar"

export default function AvatarFallbackExample() {
  return (
    <div className="flex items-center gap-4">
      <Avatar>
        <AvatarFallback>GH</AvatarFallback>
      </Avatar>
      <Avatar>
        <AvatarImage src="" alt="Linus Torvalds" />
        <AvatarFallback>LT</AvatarFallback>
      </Avatar>
    </div>
  )
}
```

### Sizes

`sm`, the default and `lg`.

```tsx
import { Avatar, AvatarFallback } from "@booleanpress/ui/avatar"

export default function AvatarSizes() {
  return (
    <div className="flex items-center gap-4">
      <Avatar size="sm">
        <AvatarFallback>AL</AvatarFallback>
      </Avatar>
      <Avatar>
        <AvatarFallback>AL</AvatarFallback>
      </Avatar>
      <Avatar size="lg">
        <AvatarFallback>AL</AvatarFallback>
      </Avatar>
    </div>
  )
}
```

### With a badge

A presence dot at the bottom corner, named for screen readers.

```tsx
import { Avatar, AvatarBadge, AvatarFallback } from "@booleanpress/ui/avatar"

export default function AvatarWithBadge() {
  return (
    <Avatar size="lg">
      <AvatarFallback>GH</AvatarFallback>
      <AvatarBadge className="bg-success">
        <span className="sr-only">Online</span>
      </AvatarBadge>
    </Avatar>
  )
}
```

### Group

Overlapping avatars with a count chip.

```tsx
import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount } from "@booleanpress/ui/avatar"

export default function AvatarGroupExample() {
  return (
    <AvatarGroup>
      <Avatar>
        <AvatarFallback>AL</AvatarFallback>
      </Avatar>
      <Avatar>
        <AvatarFallback>GH</AvatarFallback>
      </Avatar>
      <Avatar>
        <AvatarFallback>LT</AvatarFallback>
      </Avatar>
      <AvatarGroupCount>
        <span aria-hidden="true">+4</span>
        <span className="sr-only">4 more people</span>
      </AvatarGroupCount>
    </AvatarGroup>
  )
}
```

## Accessibility

**Semantics.** A `span` root, an `img` for the image once it has loaded, and a `span` for the fallback. The image renders only after it loads, so an avatar that failed to load has no `img`.

**Labels.** Give `AvatarImage` an `alt` with the person's name. Where the name is written next to the avatar, use `alt=""` so it is not read twice. Initials in the fallback are read as letters: add the name, visibly or with `aria-label` on the avatar, when nothing else names the person. Give a badge and a count chip a visually hidden text.

**Focus.** An avatar takes no focus and has no keyboard behaviour of its own, so it has no keyboard rows. Wrap it in a link or a button when it is interactive.

**Known limits.**

- The picture is not cropped by the component: `aspect-square` and the round mask do it, so a non-square image is cut.
- In a small size the badge is a plain dot (its icon is hidden at `sm`), so meaning cannot be in the icon.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### Avatar

Renders Radix Avatar.Root and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `size` | `"default" \| "sm" \| "lg"` | `default` | `sm` 24 px, `default` 32 px or `lg` 40 px. Also set as `data-size`, which the badge and the group read. |

### AvatarImage

Renders Radix Avatar.Image and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `onLoadingStatusChange` | `((status: ImageLoadingStatus) => void)` |  | Called with `loading`, `loaded` or `error` as the image's state changes. |

### AvatarFallback

Renders Radix Avatar.Fallback and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `asChild` | `boolean` |  |  |
| `delayMs` | `number` |  | Wait this many milliseconds before showing the fallback, so a fast image never flashes it. |

### AvatarBadge

Renders a `span` and passes it every other prop.

### AvatarGroup

Renders a `div` and passes it every other prop.

### AvatarGroupCount

Renders a `div` and passes it every other prop.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="avatar"` (Avatar), `data-slot="avatar-image"` (AvatarImage), `data-slot="avatar-fallback"` (AvatarFallback), `data-slot="avatar-badge"` (AvatarBadge), `data-slot="avatar-group"` (AvatarGroup), `data-slot="avatar-group-count"` (AvatarGroupCount), and `data-size`.

## Theming

The fallback is `--muted` with `--muted-foreground` text; the badge is `--primary`, and the ring that separates avatars is `--background`. Pass a `className` on the badge to use another token, as the badge example does with `bg-success`.

| Token | Used for |
| --- | --- |
| `--background` | focus ring |
| `--muted` | background |
| `--muted-foreground` | text |
| `--primary` | background |
| `--primary-foreground` | text |
