# Avatar (Caseroom component)

A photo, or initials when there is none. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

## 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`
- HTML: https://www.themes.dev/caseroom/r/html/avatar.html (with https://www.themes.dev/caseroom/r/html/caseroom.css)

Page: https://www.themes.dev/caseroom/components/avatar/ · Rules for agents: https://www.themes.dev/caseroom/AGENT.md

## Code

### avatar.astro

```astro
---
import type { HTMLAttributes } from 'astro/types';
import { cn } from '@/lib/utils';
import { avatarVariants, initials, type AvatarVariants } from './avatar.variants';

type Props = HTMLAttributes<'span'> & AvatarVariants & { name: string; src?: string };
const { name, src, size, class: className, ...rest } = Astro.props;
---

<span class={cn(avatarVariants({ size }), className)} {...rest}>
  {src ? <img src={src} alt={name} class="size-full object-cover" /> : <span role="img" aria-label={name}>{initials(name)}</span>}
</span>
```

### avatar.tsx

```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

```tsx
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('');
}
```
