Propel

Avatar Group

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

ALGHLT+2
Show code
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>
  );
}

Installation

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

Usage

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.

AGL
ALGHLT
ALGHLT
Show code
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.

ALGHLT
ALGH+2
Show code
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.

ALGH
Show code
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.

DSENPRRSMK
Show code
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.

ALGHLT
Show code
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.

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 tooltips share open/close timing when sweeping across faces.

PropTypeDefaultDescription
size(required)"2xs" | "xs" | "sm"—Shared size for every avatar in the group; an avatar's own size overrides it.
renderReactElement<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.
maxnumber2How 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.
totalnumber—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.
labelstring—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.
overflowLabelstring—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.
overflowTooltipstring—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.

PropTypeDefaultDescription
renderReactElement<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.
srcstring—Image URL. When omitted, or while it is loading/failing, the fallback shows.
altstring—Accessible name for the avatar (the person it represents).
fallbackReactNode—Initials shown when there is no image. When omitted too, a person icon shows.
delaynumber—Milliseconds before the fallback shows, to avoid a flash while src loads quickly.
tooltipstring | 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.

PropTypeDefaultDescription
renderReactElement<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"—