Shadcn Avatar for React and Tailwind
Displays a user's profile image with fallback.
VV
CN
VV
SA
PVInstallation
bunx --bun shadcn@latest add https://kit.dev/r/avatar.jsonpnpm dlx shadcn@latest add https://kit.dev/r/avatar.jsonnpx shadcn@latest add https://kit.dev/r/avatar.jsonyarn shadcn@latest add https://kit.dev/r/avatar.json<Step>This component depends on Status. Install it first if you haven't already.</Step>
Install the following dependencies:
bun add @ark-ui/react tailwind-variantspnpm add @ark-ui/react tailwind-variantsnpm install @ark-ui/react tailwind-variantsyarn add @ark-ui/react tailwind-variantsCopy and paste the following code into your project.
"use client";
import {
Avatar as ArkAvatar,
useAvatar as useArkAvatar,
useAvatarContext as useArkAvatarContext,
} from "@ark-ui/react/avatar";
import { ark } from "@ark-ui/react/factory";
import type React from "react";
import { tv, type VariantProps } from "tailwind-variants";
import { cn } from "@/lib/utils";
import {
Status,
type statusVariants,
} from "@/components/ui/status";
export const useAvatar = useArkAvatar;
export const useAvatarContext = useArkAvatarContext;
export const AvatarContext = ArkAvatar.Context;
const avatarVariants = tv({
base: [
"group/avatar",
"relative",
"size-8",
"inline-flex shrink-0 items-center justify-center",
"bg-background",
"select-none font-medium text-xs",
"rounded-full",
"after:absolute after:inset-0 after:rounded-[inherit] after:border after:border-border after:mix-blend-darken dark:after:mix-blend-lighten",
],
variants: {
size: {
sm: "size-6",
md: "size-8",
lg: "size-10",
},
},
defaultVariants: {
size: "md",
},
});
interface AvatarProps
extends React.ComponentProps<typeof ArkAvatar.Root>,
VariantProps<typeof avatarVariants> {}
export const Avatar = (props: AvatarProps) => {
const { size = "md", className, ...rest } = props;
return (
<ArkAvatar.Root
className={cn(avatarVariants({ size }), className)}
data-size={size}
data-slot="avatar"
{...rest}
/>
);
};
export const AvatarRootProvider = (
props: React.ComponentProps<typeof ArkAvatar.RootProvider> &
VariantProps<typeof avatarVariants>
) => {
const { size = "md", className, ...rest } = props;
return (
<ArkAvatar.RootProvider
className={cn(avatarVariants({ size }), className)}
data-size={size}
data-slot="avatar"
{...rest}
/>
);
};
export const AvatarImage = (
props: React.ComponentProps<typeof ArkAvatar.Image>
) => {
const { className, ...rest } = props;
return (
<ArkAvatar.Image
className={cn(
"size-full",
"aspect-square object-cover",
"rounded-[inherit]",
className
)}
data-slot="avatar-image"
{...rest}
/>
);
};
export const AvatarFallback = (
props: React.ComponentProps<typeof ArkAvatar.Fallback>
) => {
const { className, ...rest } = props;
return (
<ArkAvatar.Fallback
className={cn(
"size-full",
"flex items-center justify-center",
"bg-muted",
"rounded-[inherit]",
"[&_svg]:size-4 group-data-[size=lg]/avatar:[&_svg]:size-4.5 group-data-[size=sm]/avatar:[&_svg]:size-3",
className
)}
data-slot="avatar-fallback"
{...rest}
/>
);
};
interface AvatarBadgeProps
extends React.ComponentProps<typeof ark.span>,
Pick<VariantProps<typeof statusVariants>, "variant"> {}
export const AvatarBadge = (props: AvatarBadgeProps) => {
const { variant, className, ...rest } = props;
return (
<Status
className={cn(
"absolute inset-e-0 bottom-0 z-10",
"flex items-center justify-center",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&_svg]:hidden",
"group-data-[size=md]/avatar:size-2.5 group-data-[size=md]/avatar:[&_svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&_svg]:size-2",
className
)}
data-slot="avatar-badge"
variant={variant}
{...rest}
/>
);
};
export const AvatarGroup = (props: React.ComponentProps<typeof ark.div>) => {
const { className, ...rest } = props;
return (
<ark.div
className={cn(
"flex -space-x-2",
"**:data-[slot=avatar]:ring-2 **:data-[slot=avatar]:ring-background",
className
)}
data-slot="avatar-group"
{...rest}
/>
);
};
export const AvatarGroupCount = (
props: React.ComponentProps<typeof ark.div>
) => {
const { className, ...rest } = props;
return (
<ark.div
className={cn(
"relative",
"size-8",
"flex shrink-0 items-center justify-center",
"bg-muted",
"select-none text-muted-foreground text-sm",
"rounded-full",
"ring-2 ring-background",
"[&_svg]:size-4",
className
)}
data-slot="avatar-group-count"
{...rest}
/>
);
};Update the import paths to match your project setup.
Anatomy
Avatar
├── AvatarImage
└── AvatarFallback
AvatarGroup
├── AvatarGroupCount
└── Avatar
├── AvatarImage
├── AvatarFallback
└── AvatarBadgeUsage
import {
Avatar,
AvatarImage,
AvatarFallback
} from "@/components/ui/avatar";<Avatar>
<AvatarImage src="https://github.com/vinihvc.png" />
<AvatarFallback>VV</AvatarFallback>
</Avatar>Events
Use onStatusChange to listen for image load and error.
Root Provider
Use useAvatar with AvatarRootProvider when you need the API outside the tree.
Sizes
Small
Medium
Large
Examples
Avatar Group
Badge
Badge with Icon
Fallback with icon
Context
Use AvatarContext or useAvatarContext to read whether the image has loaded.
Avatar Group Count
Avatar Group Count Icon
Avatar with Popover
Avatar with Hover Card
Custom Size
Use size-* to set the size of the avatar.
You can use breakpoint utilities to change the size at different screen sizes.
md:size-4 lg:size-8Custom Radius
Use rounded-* to set the radius of the avatar.
You can use breakpoint utilities to change the radius at different screen sizes.
md:rounded-full lg:rounded-lgGuides
Next.js Image
AvatarImage is a native img. To use next/image, merge Ark image props with getImageProps and prefer visibility over hidden:
import { getImageProps, type ImageProps } from "next/image";
import { Avatar, AvatarFallback, useAvatarContext } from "@/components/ui/avatar";
const AvatarNextImage = (props: ImageProps) => {
const avatar = useAvatarContext();
const { hidden, ...arkImageProps } = avatar.getImageProps();
const nextImage = getImageProps(props);
return (
<img
{...arkImageProps}
{...nextImage.props}
alt={props.alt}
style={{
...props.style,
visibility: hidden ? "hidden" : "visible",
}}
/>
);
};
<Avatar>
<AvatarFallback>JD</AvatarFallback>
<AvatarNextImage alt="" height={80} src="..." width={80} />
</Avatar>API Reference
asChild merges props onto a single child element.
Avatar
Root. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "md" | sm is size-6, md is size-8, lg is size-10. Override with size-*. |
asChild | boolean | false | Render the child element instead of a div. |
className | string | - | Class names on the root. |
dir | "ltr" | "rtl" | - | Text direction. Usually inherited from LocaleProvider. |
id | string | - | Unique id for the machine. |
ids | Partial<{ root: string; image: string; fallback: string }> | - | Element ids for composition. |
onStatusChange | (details: StatusChangeDetails) => void | - | Called when loading status changes. StatusChangeDetails is { status: "loaded" | "error" }. |
| Attribute | Description |
|---|---|
data-slot | avatar |
data-size | "sm", "md", or "lg" |
data-scope | avatar |
data-part | root |
AvatarImage
Profile image. Hidden until loaded. Renders an img.
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | - | Image URL. |
alt | string | - | Accessible name. Required when the avatar represents a person. |
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the image. |
| Attribute | Description |
|---|---|
data-slot | avatar-image |
data-scope | avatar |
data-part | image |
data-state | "visible" when loaded, "hidden" otherwise |
AvatarFallback
Shown while the image is loading or after error. Renders a span. Initials or a decorative icon.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the fallback. |
| Attribute | Description |
|---|---|
data-slot | avatar-fallback |
data-scope | avatar |
data-part | fallback |
data-state | "visible" when the image is not loaded, "hidden" when it is |
AvatarBadge
Status indicator on the end-bottom corner. Renders Status. Hidden icons at size="sm".
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "success" | "info" | "warning" | "destructive" | "default" | Status color. |
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the badge. |
| Attribute | Description |
|---|---|
data-slot | avatar-badge |
AvatarGroup
Overlapping row of avatars. Renders a div.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the group. |
| Attribute | Description |
|---|---|
data-slot | avatar-group |
AvatarGroupCount
Overflow count (+2). Renders a div. Size it with size-* to match the avatars.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merge onto a single child. |
className | string | - | Class names on the count. |
| Attribute | Description |
|---|---|
data-slot | avatar-group-count |
AvatarRootProvider
Root alternative that takes the API from useAvatar.
| Prop | Type | Default | Description |
|---|---|---|---|
value | UseAvatarReturn | required | Return value of useAvatar(). |
size | "sm" | "md" | "lg" | "md" | Same size tokens as Avatar. |
asChild | boolean | false | Render the child element instead of a div. |
className | string | - | Class names on the root. |
useAvatar
Creates the avatar API for AvatarRootProvider.
const avatar = useAvatar();
avatar.setSrc("https://example.com/photo.png");AvatarContext / useAvatarContext
Render-prop or hook access to load state. Use inside Avatar or AvatarRootProvider.
| Property | Type | Description |
|---|---|---|
loaded | boolean | Whether the image has loaded. |
setSrc | (src: string) => void | Set the image src. |
setLoaded | () => void | Mark the image as loaded. |
setError | () => void | Mark the image as failed. |
AvatarContext children: (context) => ReactNode.
Accessibility
The image alt should name the person or entity. If there is no image and the fallback is only an icon, set aria-label on Avatar. Decorative icons in the fallback or badge should be aria-hidden="true".
Keyboard support
The avatar is not interactive. If you wrap it in a link or button (asChild or a parent control), that control’s keys apply (Enter, Tab).