Built on @radix-ui/react-avatar. No size variants — defaults to 40px, override with className for anything else.
npx chandra-asri-ui add avatarimport {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/app/components/ui/avatar";
/**
* Second avatar points at a broken image URL on purpose — `AvatarImage`
* hides itself and `AvatarFallback` takes over automatically, no manual
* error handling involved.
*/
export function AvatarDemo() {
return (
<div className="flex items-center gap-4">
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
<AvatarFallback>SC</AvatarFallback>
</Avatar>
<Avatar>
<AvatarImage src="https://broken.example/none.png" alt="Broken" />
<AvatarFallback>BR</AvatarFallback>
</Avatar>
<Avatar className="h-16 w-16">
<AvatarFallback>LG</AvatarFallback>
</Avatar>
</div>
);
}
AvatarImage hides itself and AvatarFallback shows itself automatically when the image fails to load (404, network error, etc) — Radix tracks the loading state internally, no onError handler needed. The second avatar in the example above points at a broken URL to demonstrate this. AvatarFallback also accepts a delayMs prop (unset here, so it shows immediately) if you want to avoid a fallback flash while a fast image is still loading.
"use client";
import { cn } from "@/lib/utils";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import * as React from "react";
/**
* Built on @radix-ui/react-avatar. Its main advantage over a plain `<img>`:
* `AvatarImage` automatically hides itself and `AvatarFallback`
* automatically shows itself when the image fails to load (404, network
* error, etc) — Radix tracks `idle` -> `loading` -> `loaded`/`error`
* internally, no manual `onError` handler needed at the call site.
* `AvatarFallback` also accepts a `delayMs` prop (left at its default here,
* so the fallback shows immediately with no delay) — set it if you want to
* avoid a fallback "flash" while a fast-loading image is still in flight.
*
* No size variants — `Avatar` defaults to 40px (`h-10 w-10`) and every
* other size is a one-off `className` override at the call site (e.g.
* `h-32 w-32` for a profile-page avatar, `h-20 w-20` for a team card).
*
* `AvatarFallback`'s background is `bg-surface-alt`, not the source's
* `bg-muted` — this bundle's `--muted` token is a dark slate meant for
* muted *text*, not a background; `surface-alt` is the actual light-neutral
* equivalent.
*/
export const Avatar = React.forwardRef<
React.ComponentRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className,
)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
export const AvatarImage = React.forwardRef<
React.ComponentRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
export const AvatarFallback = React.forwardRef<
React.ComponentRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"bg-surface-alt flex h-full w-full items-center justify-center rounded-full",
className,
)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;