# Avatar Group

An overlapping stack of avatars that share one size and show a "+N" overflow count.

## Installation

```tsx
import { Avatar } from "@makeplane/propel/components/avatar";
import { AvatarGroup } from "@makeplane/propel/components/avatar-group";
```
## Usage

```tsx
import { Avatar } from "@makeplane/propel/components/avatar";
import { AvatarGroup } from "@makeplane/propel/components/avatar-group";

export default function BasicDemo() {
  return (
    <AvatarGroup size="sm" max={3}>
      <Avatar alt="Ada Lovelace" src="https://i.pravatar.cc/64?img=47" />
      <Avatar alt="Grace Hopper" src="https://i.pravatar.cc/64?img=32" />
      <Avatar alt="Linus Torvalds" />
      <Avatar alt="Katherine Johnson" />
      <Avatar alt="Barbara Liskov" />
    </AvatarGroup>
  );
}
```
## Examples

### Sizes

`size` on the group sizes every avatar at once — an explicit child `size` overrides it. Groups support the three Figma sizes: `2xs`, `xs`, and `sm`. Stacking is for compact density, so there is deliberately no larger group.

```tsx
import { Avatar } from "@makeplane/propel/components/avatar";
import { AvatarGroup, type AvatarGroupSize } from "@makeplane/propel/components/avatar-group";

const SIZES: AvatarGroupSize[] = ["2xs", "xs", "sm"];

export default function SizesDemo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      {SIZES.map((size) => (
        <AvatarGroup key={size} size={size}>
          <Avatar alt="Ada Lovelace" fallback="AL" src="https://i.pravatar.cc/64?img=47" />
          <Avatar alt="Grace Hopper" fallback="GH" src="https://i.pravatar.cc/64?img=32" />
          <Avatar alt="Linus Torvalds" fallback="LT" />
        </AvatarGroup>
      ))}
    </div>
  );
}
```

### Overflow

`max` caps the visible faces and collapses the rest into a `+N` counter. It defaults to `2`, so a group without `max` shows two faces and a counter once there are four members — three still show whole via the threshold below. Pass `max={Infinity}` for no cap. The counter is the group's own — it takes the group's size and shape, and the fixed orange fill specified by the Figma instance.

The visible counter caps at `+9` for all supported sizes (16/20/24px); its default accessible label still announces the actual hidden-member count.

Fragments and arrays are flattened before overflow is calculated. Custom components count as single members; each should render one avatar.

The threshold is deliberate: at `max + 1` the extra face renders instead of a counter, because a `+1` takes the same width and tells you less. One member beyond that and the counter appears.

Pass `total` when you render only the faces you have resolved — a large membership never has to materialise in full, and the counter still reflects the real number. Give the counter a meaningful name with `overflowLabel`; it defaults to `"N more"`, and naming who is hidden is better still.

```tsx
import { Avatar } from "@makeplane/propel/components/avatar";
import { AvatarGroup } from "@makeplane/propel/components/avatar-group";

export default function OverflowDemo() {
  return (
    <div className="flex flex-col gap-4">
      {/* At max + 1 the extra face renders instead of a counter — a "+1" costs the same width. */}
      <AvatarGroup size="sm">
        <Avatar alt="Ada Lovelace" src="https://i.pravatar.cc/64?img=47" />
        <Avatar alt="Grace Hopper" src="https://i.pravatar.cc/64?img=32" />
        <Avatar alt="Linus Torvalds" />
      </AvatarGroup>

      {/* One more member and the counter takes over. */}
      <AvatarGroup size="sm">
        <Avatar alt="Ada Lovelace" src="https://i.pravatar.cc/64?img=47" />
        <Avatar alt="Grace Hopper" src="https://i.pravatar.cc/64?img=32" />
        <Avatar alt="Linus Torvalds" />
        <Avatar alt="Katherine Johnson" />
      </AvatarGroup>

      {/* `total` counts members the caller never resolved — only two avatars exist here. */}
      <AvatarGroup
        size="sm"
        max={3}
        total={5000}
        label="Project members"
        overflowLabel="4998 more members"
      >
        <Avatar alt="Ada Lovelace" src="https://i.pravatar.cc/64?img=47" />
        <Avatar alt="Grace Hopper" src="https://i.pravatar.cc/64?img=32" />
      </AvatarGroup>
    </div>
  );
}
```

### Fallbacks

An avatar shows its photo when a `src` is available. Without one it falls back to initials — derived from `alt` when you do not pass `fallback` — and finally to an anonymous person icon when no name resolves either.

```tsx
import { Avatar } from "@makeplane/propel/components/avatar";
import { AvatarGroup } from "@makeplane/propel/components/avatar-group";

export default function FallbacksDemo() {
  return (
    <AvatarGroup size="sm">
      {/* Photo, when a `src` is available. */}
      <Avatar alt="Ada Lovelace" fallback="AL" src="https://i.pravatar.cc/64?img=47" />
      {/* No image — falls back to initials, tinted from the member's name. */}
      <Avatar alt="Grace Hopper" fallback="GH" />
      {/* No image and no initials — falls back to an anonymous person icon. */}
      <Avatar />
    </AvatarGroup>
  );
}
```

### Variants

Each initials avatar's background variant is derived stably from the member's name — there is no `variant` prop.

```tsx
import { Avatar } from "@makeplane/propel/components/avatar";
import { AvatarGroup } from "@makeplane/propel/components/avatar-group";

export default function VariantsDemo() {
  return (
    // Uncapped on purpose: every member's variant needs a face to show on.
    <AvatarGroup size="sm" max={Infinity}>
      <Avatar alt="Design" fallback="DS" />
      <Avatar alt="Engineering" fallback="EN" />
      <Avatar alt="Product" fallback="PR" />
      <Avatar alt="Research" fallback="RS" />
      <Avatar alt="Marketing" fallback="MK" />
    </AvatarGroup>
  );
}
```

### With tooltips

Each member can show its name on hover via `tooltip`. Pass `overflowTooltip` to show hidden names on hover or keyboard focus of the `+N` counter. Its wrapper owns focus and a 24px target without increasing the stack width; Escape dismisses the tooltip. Omit it to keep the counter presentational. Use localized `overflowLabel` for the count and `overflowTooltip` for the names. The group shares tooltip timing, so sweeping across faces swaps names without restarting the open delay.

```tsx
import { Avatar } from "@makeplane/propel/components/avatar";
import { AvatarGroup } from "@makeplane/propel/components/avatar-group";

export default function WithTooltipsDemo() {
  return (
    <AvatarGroup
      size="sm"
      max={3}
      total={7}
      overflowLabel="4 more members"
      overflowTooltip="Katherine Johnson, Barbara Liskov, Margaret Hamilton and Alan Turing"
    >
      <Avatar alt="Ada Lovelace" tooltip fallback="AL" src="https://i.pravatar.cc/64?img=47" />
      <Avatar alt="Grace Hopper" tooltip fallback="GH" src="https://i.pravatar.cc/64?img=32" />
      <Avatar alt="Linus Torvalds" tooltip fallback="LT" />
    </AvatarGroup>
  );
}
```

### Accessibility

Use `label` for a single summary of presentational faces. When `overflowTooltip` is present, the group exposes that summary and a separately focusable counter, while the faces remain decorative. Do not use this summary mode around individually interactive faces: use a labelled semantic list with named buttons or links instead. Member tooltips in this summary mode are hover-only; do not rely on them for individually accessible information. The summary must include the hidden count because its decorative counter is not announced separately. Avatar artwork does not gain a tab stop automatically; the overflow tooltip wrapper is the explicit exception so keyboard users can read hidden names.

Migration guidance is maintained in the [0.5.0 changelog entry](https://github.com/makeplane/propel/blob/main/packages/propel/CHANGELOG.md#050).

## API Reference

### AvatarGroup

The ready-made overlapping avatar stack: shares `size` with every `Avatar` inside via context (an
avatar's own `size` still wins), composed around the styled `elements/avatar-group` container.
Members retain their standalone ring inside the group. A `TooltipProvider` wraps the stack so
member `tooltip`s share open/close timing when sweeping across faces.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` (required) | `"2xs" \| "xs" \| "sm"` | — | Shared size for every avatar in the group; an avatar's own `size` overrides it. |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `max` | `number` | `2` | How many avatars to show before collapsing the rest into the "+N" counter. Pass `Infinity` for no cap. Following the Figma rule, at `total === max + 1` the extra avatar is cheaper than a "+1", so all supplied faces show. If that extra face is not supplied, the counter still counts it. Finite values are floored and clamped to zero; non-finite values mean no cap. |
| `total` | `number` | — | The true member count, when the caller renders only the visible avatars. Lets a large set keep an accurate "+N" without materialising — or fetching — every member. Defaults to the number of children. Finite values are floored and clamped to zero. If smaller than the supplied child count, extra children are omitted; non-finite values use the child count. |
| `label` | `string` | — | Accessible name for the whole stack, e.g. "Ada Lovelace, Grace Hopper and 4 others". The spec asks for a list or a single labelled summary rather than a run of unrelated images. |
| `overflowLabel` | `string` | — | The visible counter caps at +9 for every size; the accessible name retains the full hidden count. Accessible name for the counter. Defaults to "N more"; prefer naming who is hidden. |
| `overflowTooltip` | `string` | — | Hidden-member names shown on hover or focus of the counter's wrapper. Omit for a presentational counter. Uses the shared multiline Tooltip; localize this text separately from `overflowLabel`. This non-actionable image wrapper is the explicit exception to presentational avatars staying out of the tab order: focus makes the hidden names keyboard-accessible. |

### Avatar

The ready-made avatar: an image that falls back to initials (or an anonymous person icon),
composed from the `elements/avatar` parts (`Avatar` root + `AvatarImage` + `AvatarFallback`).
Pass `src` for the photo and `fallback` for initials; the initials color is chosen automatically
and is not a consumer prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `size` | `"2xs" \| "xs" \| "sm" \| "md" \| "lg" \| "xl" \| "2xl" \| "3xl"` | `"md"` | Avatar size. Optional here — resolved from the `AvatarGroup` context, else `md`. |
| `src` | `string` | — | Image URL. When omitted, or while it is loading/failing, the fallback shows. |
| `alt` | `string` | — | Accessible name for the avatar (the person it represents). |
| `fallback` | `ReactNode` | — | Initials shown when there is no image. When omitted too, a person icon shows. |
| `delay` | `number` | — | Milliseconds before the fallback shows, to avoid a flash while `src` loads quickly. |
| `tooltip` | `string \| boolean` | — | Name shown in a tooltip on hover. Pass `true` to reuse `alt`, or a string for the label (works without `alt`). Omit for no tooltip. |

### AvatarGroupCounter

The styled "+N" overflow counter that closes an avatar stack — the last rung of the Figma "Avatar
groups" component. It is a sibling of the avatars, not an `Avatar` itself: its surface is fixed
to the orange label fill as defined in `variants.ts`.

This is the styled part only. The ready-made `AvatarGroup` from
`@makeplane/propel/components/avatar-group` derives the count from `max`/`total` and renders this
for you; reach for this element when you are composing a stack by hand.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement<unknown, string \| JSXElementConstructor<any>> \| ComponentRenderFn<HTMLProps, {}>` | — | Allows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |
| `size` | `"2xs" \| "xs" \| "sm"` | — |  |
