Skip to content
Caseroom

Components · Display

Avatar

A photo, or initials when there is none.

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/caseroom/r/avatar.json
Astro
npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/avatar.json

Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom 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('');
}