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
| Part | Role |
|---|---|
Avatar.Root | The circular frame. Takes size and tracks the image load status. |
Avatar.Image | The <img>. Rendered only once its src has loaded. |
Avatar.Fallback | Initials or an icon, shown while loading, on error, or when there's no image. |
Avatar.Badge | A small status dot anchored to the bottom-end corner. |
Avatar.Group | Overlaps a row of avatars, ringing each in the page background. Takes size. |
Avatar.GroupCount | The 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.
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.
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.
| Size | Diameter |
|---|---|
sm | 24px |
md | 32px |
lg | 40px |
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.
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.
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>