Skip to content
    Style: Wireframe

    Components · Display

    Avatar

    A photo, or initials when there is none.

    Used in UI Pro: Testimonials, Posts, Team, Story card, Hover card

    ALGHAT
    • ALAda LovelaceOwner
    • GHGrace HopperAdmin

    Both previews render the same markup; the HTML tab below is that markup without a framework.

    Install

    React
    npx shadcn@latest add https://www.themes.dev/ui/r/avatar.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/avatar.json

    Or copy the files below into your project. They need clsx, tailwind-merge, and the themes.dev UI theme. The HTML needs only the compiled CSS.

    avatar.tsx
    import type { HTMLAttributes } from 'react';
    import { cn } from '@/lib/utils';
    import { avatarVariants, initials, type AvatarVariants } from './avatar.variants';
    
    export type AvatarProps = HTMLAttributes<HTMLSpanElement> & AvatarVariants & { name: string; src?: string };
    
    /** A photo, or the initials of `name` when there is none. The name is the accessible label either way. */
    export function Avatar({ name, src, size, className, ...props }: AvatarProps) {
      return (
        <span className={cn(avatarVariants({ size }), className)} {...props}>
          {src ? <img src={src} alt={name} className="size-full object-cover" /> : <span role="img" aria-label={name}>{initials(name)}</span>}
        </span>
      );
    }
    avatar.variants.ts
    import { cva, type VariantProps } from 'class-variance-authority';
    
    export const avatarVariants = cva(
      'relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted font-medium text-muted-foreground ring-1 ring-border',
      {
        variants: { size: { sm: 'size-6 text-[10px]', default: 'size-8 text-xs', lg: 'size-11 text-sm' } },
        defaultVariants: { size: 'default' },
      },
    );
    export type AvatarVariants = VariantProps<typeof avatarVariants>;
    
    /** "Ada Lovelace" → "AL". */
    export function initials(name: string) {
      return name
        .split(/\s+/)
        .filter(Boolean)
        .slice(0, 2)
        .map((part) => part[0]!.toUpperCase())
        .join('');
    }