Avatar
An image element with a text fallback for representing a user or entity.
Installation
$
Usage
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"Examples
Default
Image with a text fallback, plus an optional status dot.
MDUI
SC
Sizes
Four sizes from one prop. The fallback text scales with the box.
MDMDMDMD
Group
Overlap with -space-x-2 and a ring-background outline; cap with a +N tile.
MDSCUI+3
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| sizeAvatar | "sm" | "default" | "lg" | "xl" | "default" | The avatar size: sm (24px), default (32px), lg (40px), or xl (48px). The fallback text scales with it, so you never set two classes. |
| srcAvatarImage | string | - | Image source URL. The image only renders once it has loaded; the fallback shows until then. |
| onLoadingStatusChangeAvatarImage | (status: "idle" | "loading" | "loaded" | "error") => void | - | Called when the image's loading status changes. |
| delayMsAvatarFallback | number | - | Delay in milliseconds before the fallback renders, avoiding a flash when the image loads quickly. |
"use client"
import * as React from "react"
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
import { cn } from "@/registry/lib/utils"
type AvatarSize = "sm" | "default" | "lg" | "xl"
// @use-when a person or account's image, with a fallback.
function Avatar({
size = "default",
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
/** 24 / 32 / 40 / 48px. The fallback's type scales with it. */
size?: AvatarSize
}) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
data-size={size}
className={cn(
"group/avatar relative flex shrink-0 overflow-hidden rounded-full",
// The box and the fallback's type are ONE decision, so the size lives
// here and the fallback reads it off `data-size` rather than asking the
// caller to send a matching `text-*`. Hand-syncing those two classes is
// the same faked-size pattern `verify-ui` flags on Button.
"data-[size=sm]:size-6",
"data-[size=default]:size-8",
"data-[size=lg]:size-10",
"data-[size=xl]:size-12",
className
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-neutral-200 text-muted-foreground dark:bg-neutral-800 flex size-full items-center justify-center rounded-full font-medium",
// Type steps with the root's size, one decision, never two classes.
"group-data-[size=sm]/avatar:text-2xs",
"group-data-[size=default]/avatar:text-xs",
"group-data-[size=lg]/avatar:text-sm",
"group-data-[size=xl]/avatar:text-base",
className
)}
{...props}
/>
)
}
export { Avatar, AvatarImage, AvatarFallback }