# Avatar

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/avatar
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=408-2

```tsx
import { Avatar } from '@commenda/pathfinder/avatar';

export const AvatarDefault = () => (
  <Avatar
    identity={{ kind: 'person', userId: 'priya', name: 'Priya Shah' }}
    size={32}
  />
);
```


## Installation [#installation]

```sh
pnpm --filter your-app add "@commenda/pathfinder@workspace:*"
```

Import `@commenda/pathfinder/styles.css` once after host CSS. Pathfinder ships compiled, prefixed Tailwind v4 CSS; the application needs no Tailwind scanning. Import from `@commenda/pathfinder/avatar`. Interactive callbacks belong in a client component in Next.js App Router.

## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `identity` | `AvatarIdentity` | required | Required discriminated person, organization, or logo object |
| `size` | `AvatarSize` | optional | 24 |
| `label` | `string` | optional | Optional accessible-name override/localized fallback |
| `decorative` | `boolean` | optional | false; true suppresses name and tooltip |
| `tooltipConfig` | `false \| { label?: string; }` | optional | Name tooltip enabled; false when visible copy already names it |
| `className` | `string` | optional | Additional classes for the component root. |
| `id` | `string` | optional | Stable HTML identifier for the component. |

### identity fields

Choose one shape; excluded fields cannot be combined.

#### Option 1

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `"person"` | Yes |  |
| `userId` | `string` | No |  |
| `name` | `string` | No |  |
| `email` | `string` | No |  |
| `src` | `string` | No |  |

#### Option 2

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `"organization"` | Yes |  |
| `name` | `string` | Yes |  |
| `src` | `string` | No |  |

#### Option 3

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `"logo"` | Yes |  |
| `label` | `string` | Yes |  |
| `artwork` | `ReactElement<any, string \| React.JSXElementConstructor<any>>` | Yes |  |

### tooltipConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `label` | `string` | No |  |

Root IDs, className, appropriate ARIA attributes, and typed refs are supported.

## Usage rules [#usage-rules]

### Identity structure [#identity-structure]

```ts
type AvatarIdentity =
  | {
      kind: 'person';
      userId?: string;
      name?: string;
      email?: string;
      src?: string;
    }
  | { kind: 'organization'; name: string; src?: string }
  | { kind: 'logo'; label: string; artwork: ReactElement };
```

Person: photo → initials → neutral person icon. Initials use name, then email local-part; grapheme-aware extraction preserves emoji and non-Latin names. Size 16 shows one initial; other sizes show up to two. Image failure keeps the same dimensions and reveals the fallback.

Tone comes from the stable user ID, never name/email. UTF-8 FNV-1a modulo eight chooses clay, apricot, ochre, olive, sage, dusk, plum, or rose using existing token pairs. Without an ID the fallback is neutral. The accessible fallback is “Unknown person”; label can localize it. Organizations use a neutral plate, logos use supplied artwork, legal entities use flags. Commenda's existing short-form mark is `Icons.Interface.CommendaBrand`.

### AvatarGroup [#avatargroup]

| Prop           | Type                                                     | Default / behavior                |
| -------------- | -------------------------------------------------------- | --------------------------------- |
| label          | string                                                   | Required group name               |
| people         | `readonly { id; name?; email?; src? }[]`                 | Stable id also determines tone    |
| size           | 20 or 24 or 32                                           | 24                                |
| surface        | white or canvas                                          | white; determines separating ring |
| overflowConfig | `{ label?(count): string; formatNames?(names): string }` | Localize disclosure and name list |
| unknownLabel   | string                                                   | Unknown person                    |

Three visible people, then +N. Names remain available on hover/focus, and the overflow supports tap. Escape or clicking outside closes the disclosure. Earlier people sit above later ones with a 1.5px surface ring. Empty groups render no fake members; hidden people must be supplied, not just a total count. Use a full people list elsewhere for very large groups.

## Examples [#examples]

### Sizes [#sizes]


```tsx
import { Avatar } from '@commenda/pathfinder/avatar';

export const AvatarSizes = () => (
  <div
    style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 16 }}
  >
    {([16, 20, 24, 32, 40, 64] as const).map((size) => (
      <Avatar
        key={size}
        size={size}
        identity={{ kind: 'person', userId: 'priya', name: 'Priya Shah' }}
      />
    ))}
  </div>
);
```


### Types [#types]


```tsx
import { Icons } from '@commenda/pathfinder';
import { Avatar } from '@commenda/pathfinder/avatar';

export const AvatarTypes = () => (
  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>
    <Avatar
      identity={{ kind: 'person', userId: 'priya', name: 'Priya Shah' }}
      size={40}
    />
    <Avatar identity={{ kind: 'person' }} size={40} />
    <Avatar
      identity={{ kind: 'organization', name: 'Acme Finance' }}
      size={40}
    />
    <Avatar
      identity={{
        kind: 'logo',
        label: 'Commenda',
        artwork: <Icons.Interface.CommendaBrand />,
      }}
      size={40}
    />
  </div>
);
```


### Groups [#groups]


```tsx
import { AvatarGroup } from '@commenda/pathfinder/avatar';

const people = [
  { id: 'priya', name: 'Priya Shah' },
  { id: 'jan', name: 'Jan Kowalski' },
  { id: 'mei', name: 'Mei Tan' },
  { id: 'ana', name: 'Ana Silva' },
  { id: 'sara', name: 'Sara Ali' },
];

export const AvatarGroups = () => (
  <div style={{ display: 'grid', gap: 24 }}>
    {([20, 24, 32] as const).map((size) => (
      <AvatarGroup
        key={size}
        size={size}
        people={people}
        label={`Assignees at size ${String(size)}`}
      />
    ))}
  </div>
);
```


### Fallback [#fallback]


```tsx
import { Avatar } from '@commenda/pathfinder/avatar';

export const AvatarFallback = () => (
  <Avatar
    identity={{
      kind: 'person',
      userId: 'priya',
      name: 'Priya Shah',
      src: '/missing-avatar.png',
    }}
    size={40}
  />
);
```


## Photo [#photo]

An image loads inside the same fixed-size frame, retaining initials if loading fails. The demo portrait is a local copy of the sample provided in Figma (identified there as an Unsplash free-license image). Replace the example URL with your application's image URL.


```tsx
import { Avatar } from '@commenda/pathfinder/avatar';

const portrait = new URL('./images/portrait.png', import.meta.url).href;

export const AvatarPhoto = () => (
  <Avatar
    identity={{
      kind: 'person',
      userId: 'priya',
      name: 'Priya Shah',
      src: portrait,
    }}
    size={64}
  />
);
```


## Design rules [#design-rules]

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

### Avatar and AvatarGroup [#avatar-and-avatargroup]

Source: [412:74](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=412-74), group 412:12893, palette 533:26418, docs 414:485.

- `AVATAR-01` — Source: people circular; organization/logo rounded plate, radii5/6/8/10/12/20 for sizes16/20/24/32/40/64. Legal entities use flags. Identity is a person/organization/logo union. Reuse the existing CommendaBrand icon for the short-form mark.
- `AVATAR-02` — Source/Pathfinder: stable user-ID FNV-1a modulo8; UTF-8 and unsigned32 arithmetic. Tone order clay/apricot/ochre/olive/sage/dusk/plum/rose; reuse token pairs. No random tone or name/email hash. Missing user ID is neutral.
- `AVATAR-03` — Source/Pathfinder: photo, then name/email initials, then neutral person. Grapheme-safe uppercase initials, one at16 and up to two otherwise. Dimensions stay reserved on image failure. Logo slots are10/12/14/18/22/36 at the six sizes, on white; neutral-person artwork is12/12/16/16/20/33.6. Full accessible name or localized fallback; decorative avatars suppress tooltip and repeated naming.
- `AVATAR-04` — Source: show three people then +N; tooltip names hidden people; group sizes20/24/32. Earlier people stack above later ones. 1.5 surface-color rings separate overlapping avatars. Do not supply count without identities.
- `AVATAR-05` — Pathfinder: names reachable on focus; overflow supports touch tap, outside press, and Escape. Localize overflow label/name list and unknown identity copy. No action semantics on ordinary avatars. Large groups need a full people list in the owning interface.

### Source discrepancies and implementation decisions [#source-discrepancies-and-implementation-decisions]
