Avatar
A user's photo, with initials to fall back on.
import { Div } from "@implementjs/core";
import { Avatar, AvatarFallback, AvatarImage } from "@/lib/components/ui/avatar";
export default function AvatarDemo() {
return Div(
{ class: "flex flex-row flex-wrap items-center gap-8" },
Avatar(
AvatarImage({ src: "https://github.com/ieedan.png", alt: "@ieedan" }),
AvatarFallback("AB"),
),
Avatar(
AvatarImage({ src: "https://github.com/broken-link-404.png/nope", alt: "broken" }),
AvatarFallback("ER"),
),
Div(
{
class:
"flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background *:data-[slot=avatar]:grayscale",
},
Avatar(
AvatarImage({ src: "https://github.com/ieedan.png", alt: "@ieedan" }),
AvatarFallback("AB"),
),
Avatar(
AvatarImage({ src: "https://github.com/github.png", alt: "@github" }),
AvatarFallback("GH"),
),
Avatar(
AvatarImage({ src: "https://github.com/shadcn.png", alt: "@shadcn" }),
AvatarFallback("CN"),
),
),
);
}Installation
npx jsrepo add @implementjs/ui/avatar
Nothing else comes with it — this one stands alone on @implementjs/core and @implementjs/primitives.
Copy the file below to src/lib/components/ui/avatar.ts. It imports cn, so copy utils.ts to src/lib/utils.ts too.
import type { Child, ComponentProps } from "@implementjs/core";
import {
Avatar as AvatarPrimitive,
AvatarFallback as AvatarFallbackPrimitive,
AvatarImage as AvatarImagePrimitive,
} from "@implementjs/primitives";
import { createComponent } from "@implementjs/primitives";
import { cn } from "@/lib/utils";
export type AvatarProps = ComponentProps<typeof AvatarPrimitive>;
export type AvatarImageProps = ComponentProps<typeof AvatarImagePrimitive>;
export type AvatarFallbackProps = ComponentProps<typeof AvatarFallbackPrimitive>;
export const Avatar = createComponent(function Avatar(
{ class: className, ...props }: AvatarProps,
...children: Child[]
) {
return AvatarPrimitive(
{
...props,
"data-slot": "avatar",
class: cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className),
},
...children,
);
});
export const AvatarImage = createComponent(function AvatarImage({
class: className,
...props
}: AvatarImageProps) {
return AvatarImagePrimitive({
...props,
"data-slot": "avatar-image",
class: cn("aspect-square size-full", className),
});
});
export const AvatarFallback = createComponent(function AvatarFallback(
{ class: className, ...props }: AvatarFallbackProps,
...children: Child[]
) {
return AvatarFallbackPrimitive(
{
...props,
"data-slot": "avatar-fallback",
class: cn("flex size-full items-center justify-center rounded-full bg-muted", className),
},
...children,
);
});Usage
Avatar is a 2rem circle that clips whatever is inside it. AvatarImage fills it, and AvatarFallback takes over when the image is still loading or has failed — the primitive tracks the load, so there is no flash of initials behind a photo that arrives.
import { Avatar, AvatarFallback, AvatarImage } from "@/lib/components/ui/avatar";
Avatar(AvatarImage({ src: "https://github.com/ieedan.png", alt: "@ieedan" }), AvatarFallback("AB"));
Sizing and stacking
Size is a class on the root, and the parts follow it:
Avatar({ class: "size-12" }, AvatarImage({ src, alt: "" }), AvatarFallback("AB"));
For an overlapping row, give the group a negative gap and ring each avatar in the page background so the edges stay readable:
Div(
{ class: "flex -space-x-2" },
Avatar({ class: "ring-2 ring-background" }, AvatarFallback("AB")),
Avatar({ class: "ring-2 ring-background" }, AvatarFallback("CD")),
);
API Reference
Every prop the styling does not consume is forwarded to the Avatar primitive, so the tables below are the whole surface — the behavior props and the styling ones together.
Avatar
The root. Tracks the image's loading status for the parts inside it. Styled as a 2rem circle that clips whatever is inside it. Renders a Div; extra props are forwarded onto it.
| Prop | Type | Default | Description |
|---|---|---|---|
delayMs | number | 0 | How long to wait after the image loads before showing it, preventing a flash on fast connections. |
onLoadingStatusChange | (status: AvatarLoadingStatus) => void | — | Called whenever the loading status changes. |
| Data attribute | Value |
|---|---|
[data-avatar-root] | Present |
[data-status] | "loading" | "loaded" | "error" |
AvatarImage
The picture. Preloaded off-DOM and only shown once loaded; a reactive src re-runs the load. Renders a Img; extra props are forwarded onto it.
| Data attribute | Value |
|---|---|
[data-avatar-image] | Present |
[data-status] | "loading" | "loaded" | "error" |
AvatarFallback
Shown until the image has loaded — initials, an icon, anything. Styled as a filled circle that centers its content. Renders a Span; extra props are forwarded onto it.
| Data attribute | Value |
|---|---|
[data-avatar-fallback] | Present |
[data-status] | "loading" | "loaded" | "error" |



