# Avatar

Avatar is a neobrutalist React component from Cruise UI. A photo from any URL, falling back to initials or an icon; circle or square, 32–80px, status dots, auto colours and groups.

Install (with the @cruiseui registry in components.json, see Installation):

```bash
npx shadcn@latest add @cruiseui/avatar
```

Import: `import { CruiseAvatar, CruiseAvatarGroup } from "@/components/cruise/avatar";`

## When to use

Showing a person (or a team or brand, with the square shape) next to their name, comments or a list.

## When not to

Product photos (use an image in a Card).

## Using images from S3 / Cloudinary

- Any image URL works: S3, Cloudinary, Firebase Storage, Gravatar or your own server.
- Public URLs and signed URLs both work. For a private bucket, pass a signed URL; if it expires or returns 403, the initials show instead.
- To use next/image, pass imageComponent={Image} and add the image host to images.remotePatterns in next.config.
- If your site sends a Content-Security-Policy, add the image host to img-src.

## Example (React)

```tsx
import type { ReactNode } from "react";
import { CruiseAvatar, CruiseAvatarGroup } from "@/components/cruise/avatar";

const team = [
  { name: "Ada Okafor", src: "https://i.pravatar.cc/120?img=32" },
  { name: "Tunde Bello" },
  { name: "Ngozi Eze", src: "https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_160,w_160/woman.jpg" },
  { name: "Musa Garba" },
  { name: "Kemi Adeyemi" },
  { name: "Femi Ojo" },
  { name: "Zainab Musa" },
];

function Row({ title, children }: { title: string; children: ReactNode }) {
  return (
    <div className="flex flex-col gap-3">
      <p className="text-label uppercase text-ink-muted">{title}</p>
      <div className="flex flex-wrap items-center gap-4">{children}</div>
    </div>
  );
}

export default function AvatarExample() {
  return (
    <div className="flex flex-col gap-6">
      <Row title="Image from any URL">
        <CruiseAvatar src="https://i.pravatar.cc/120?img=12" alt="Idris Bankole" name="Idris Bankole" size="lg" />
        <CruiseAvatar
          src="https://res.cloudinary.com/demo/image/upload/c_thumb,g_face,h_160,w_160/woman.jpg"
          alt="Ngozi Eze"
          name="Ngozi Eze"
          size="lg"
        />
      </Row>
      <Row title="Initials, icon and a broken URL">
        <CruiseAvatar name="Idris Bankole" tone="auto" />
        <CruiseAvatar name="Ada" tone="auto" />
        <CruiseAvatar initials="CU" name="Cruise UI" tone="primary" />
        <CruiseAvatar alt="Guest" />
        <CruiseAvatar src="https://res.cloudinary.com/demo/image/upload/missing-photo.jpg" alt="Musa Garba" name="Musa Garba" tone="auto" />
      </Row>
      <Row title="Status, sizes and square">
        <CruiseAvatar name="Tunde Bello" tone="auto" size="sm" status="online" />
        <CruiseAvatar name="Kemi Adeyemi" tone="auto" status="away" />
        <CruiseAvatar name="Femi Ojo" tone="auto" size="lg" status="busy" />
        <CruiseAvatar name="Zainab Musa" tone="auto" size="xl" status="offline" />
        <CruiseAvatar name="Danfo Logistics" shape="square" tone="primary" size="lg" />
      </Row>
      <CruiseAvatarGroup people={team} max={5} label="Team" />
    </div>
  );
}
```

## HTML

Needs theme.css (https://cruiseui.dev/theme.css).

```html
<span class="avatar avatar-lg">
  <span class="avatar-fallback" role="img" aria-label="Idris Bankole">IB</span>
  <img class="avatar-image" src="https://example.com/idris.jpg" alt="Idris Bankole" loading="lazy" decoding="async"
       data-loading onload="this.removeAttribute('data-loading')" onerror="this.setAttribute('data-error', '')">
  <span class="avatar-status" data-status="online" aria-hidden="true"></span>
  <span class="visually-hidden">, online</span>
</span>

<span class="avatar" data-shape="square" data-tone="primary">
  <span class="avatar-fallback" role="img" aria-label="Danfo Logistics">DL</span>
</span>

<div class="avatar-group" role="group" aria-label="Team">
  <span class="avatar" data-tone="info"><span class="avatar-fallback" role="img" aria-label="Ada Okafor">AO</span></span>
  <span class="avatar" data-tone="success"><span class="avatar-fallback" role="img" aria-label="Ngozi Eze">NE</span></span>
  <span class="avatar avatar-more" role="img" aria-label="and 3 more">+3</span>
</div>
```

## Props: CruiseAvatar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `name?` | `string` | — | The person's name: the initials and the fallback's accessible name. |
| `initials?` | `string` | — | Overrides the initials from `name` (max 2 characters). |
| `icon?` | `ReactNode` | — | Fallback when there's no image, name or initials (default: Lucide user). |
| `size?` | `number \| CruiseAvatarSize` | `"md"` | 32 / 40 / 56 / 80px, or any number of pixels. |
| `shape?` | `"circle" \| "square"` | `"circle"` | Circle (default) or a square that follows var(--radius), for brands and teams. |
| `status?` | `CruiseAvatarStatus` | — | — |
| `tone?` | `CruiseAvatarTone` | `"muted"` | Background behind the initials; "auto" picks one from the name. |
| `imageComponent?` | `ComponentType<CruiseAvatarImageProps>` | — | Renders the image with your component, e.g. next/image (add the host to images.remotePatterns in next.config). Gets src, alt, width, height. |
| `referrerPolicy?` | `HTMLAttributeReferrerPolicy` | — | — |
| `crossOrigin?` | `"anonymous" \| "use-credentials"` | — | — |
| `className?` | `string` | — | — |
| `src` | `string \| undefined` | — | — |
| `alt` | `string` | — | — |

## Props: CruiseAvatarGroup

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `people` | `CruiseAvatarPerson[]` | — | — |
| `max?` | `number` | — | How many avatars to show before the "+n" counter (all by default). |
| `size?` | `number \| CruiseAvatarSize` | `"md"` | — |
| `shape?` | `"circle" \| "square"` | `"circle"` | — |
| `tone?` | `CruiseAvatarTone` | `"auto"` | — |
| `label?` | `string` | `"People"` | Accessible name for the group, e.g. "Team". |
| `className?` | `string` | — | — |

## Accessibility

- The image uses its alt text; the initials or icon fallback is role="img" named after the person.
- Next to a visible name, pass alt="" (and no name) so it isn't read twice.
- Status is said in words too (", online"), not only by the dot's colour.
- The fallback shows while the image loads and if it fails: never a broken-image icon.
