Avatar

A circular image representing a person or entity, with the same parts and props as the shadcn / Base UI avatar. There is no React Aria primitive for avatars, so the component owns its one piece of state — the image's load status — and uses it to show Fallback until the image has actually loaded. Consumers never see a broken-image glyph or an empty disc.

import { Avatar } from "@pockets-ui/components"

Anatomy

PartRole
Avatar.RootThe circular frame. Takes size and tracks the image load status.
Avatar.ImageThe <img>. Rendered only once its src has loaded.
Avatar.FallbackInitials or an icon, shown while loading, on error, or when there's no image.
Avatar.BadgeA small status dot anchored to the bottom-end corner.
Avatar.GroupOverlaps a row of avatars, ringing each in the page background. Takes size.
Avatar.GroupCountThe trailing "+N" disc in a group, sized by the group's size.

Basic

Always pair Image with a Fallback; the fallback is what renders until the image arrives. Give the image an alt naming the person.

PK
Code
import { Avatar } from "@pockets-ui/components"

export default function AvatarBasic() {
  return (
    <Avatar.Root>
      <Avatar.Image
        src="https://api.dicebear.com/9.x/shapes/svg?seed=pockets"
        alt="Pockets"
      />
      <Avatar.Fallback>PK</Avatar.Fallback>
    </Avatar.Root>
  )
}

Fallback

The fallback shows whenever the image is missing, still loading, or failed to load. Use initials or an icon. Pass delay (ms) to hold the fallback back briefly so a fast-loading image doesn't flash initials first.

IN
Code
import { Avatar } from "@pockets-ui/components"

export default function AvatarFallback() {
  return (
    <>
      <Avatar.Root>
        <Avatar.Image src="/this-image-does-not-exist.png" alt="Investor" />
        <Avatar.Fallback>IN</Avatar.Fallback>
      </Avatar.Root>
      <Avatar.Root>
        <Avatar.Fallback>
          <svg
            aria-hidden="true"
            width="16"
            height="16"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="2"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
            <circle cx="12" cy="7" r="4" />
          </svg>
        </Avatar.Fallback>
      </Avatar.Root>
    </>
  )
}

Sizes

size is a recipe variant on Root. The fallback text and badge pick their size up from Root through Panda's style context, so the inner parts take no size prop of their own.

SizeDiameter
sm24px
md32px
lg40px
SMMDLG
Code
import { Avatar } from "@pockets-ui/components"

export default function AvatarSizes() {
  return (
    <>
      <Avatar.Root size="sm">
        <Avatar.Fallback>SM</Avatar.Fallback>
      </Avatar.Root>
      <Avatar.Root size="md">
        <Avatar.Fallback>MD</Avatar.Fallback>
      </Avatar.Root>
      <Avatar.Root size="lg">
        <Avatar.Fallback>LG</Avatar.Fallback>
      </Avatar.Root>
    </>
  )
}

Badge

Badge is an empty presence dot by default, ringed in the page background so it separates from the image. Recolour it with css. On md and lg avatars it can hold a small icon; on sm the icon is hidden. A badge carries no semantics on its own — add aria-label when its colour means something.

ONAWPL
Code
import { Avatar } from "@pockets-ui/components"

export default function AvatarBadge() {
  return (
    <>
      <Avatar.Root>
        <Avatar.Fallback>ON</Avatar.Fallback>
        <Avatar.Badge css={{ backgroundColor: "bg.success.inverse" }} />
      </Avatar.Root>
      <Avatar.Root>
        <Avatar.Fallback>AW</Avatar.Fallback>
        <Avatar.Badge css={{ backgroundColor: "bg.danger.inverse" }} />
      </Avatar.Root>
      <Avatar.Root>
        <Avatar.Fallback>PL</Avatar.Fallback>
        <Avatar.Badge aria-label="Verified">
          <svg
            aria-hidden="true"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            strokeWidth="4"
            strokeLinecap="round"
            strokeLinejoin="round"
          >
            <path d="M20 6 9 17l-5-5" />
          </svg>
        </Avatar.Badge>
      </Avatar.Root>
    </>
  )
}

Group

Group overlaps its children and rings each one in the page background. It is a style-context provider like Root: its size sizes the trailing GroupCount, so pass the same size to the group as to its avatars.

ABCDEF
+4
ABCDEF
+4
ABCDEF
+4
Code
import { Avatar } from "@pockets-ui/components"

const initials = ["AB", "CD", "EF"]

export default function AvatarGroup() {
  return (
    <>
      <Avatar.Group aria-label="Deal team" size="sm">
        {initials.map((i) => (
          <Avatar.Root key={i} size="sm">
            <Avatar.Fallback>{i}</Avatar.Fallback>
          </Avatar.Root>
        ))}
        <Avatar.GroupCount>+4</Avatar.GroupCount>
      </Avatar.Group>
      <Avatar.Group aria-label="Deal team">
        {initials.map((i) => (
          <Avatar.Root key={i}>
            <Avatar.Fallback>{i}</Avatar.Fallback>
          </Avatar.Root>
        ))}
        <Avatar.GroupCount>+4</Avatar.GroupCount>
      </Avatar.Group>
      <Avatar.Group aria-label="Deal team" size="lg">
        {initials.map((i) => (
          <Avatar.Root key={i} size="lg">
            <Avatar.Fallback>{i}</Avatar.Fallback>
          </Avatar.Root>
        ))}
        <Avatar.GroupCount>+4</Avatar.GroupCount>
      </Avatar.Group>
    </>
  )
}

One-off overrides

Every part takes a css prop for style overrides that don't deserve a recipe variant.

<Avatar.Root css={{ width: "16", height: "16" }}>
  <Avatar.Fallback css={{ textStyle: "body.large.semibold" }}>PK</Avatar.Fallback>
</Avatar.Root>