Skip to content
Dethink Components

ComponentsAvatar

Avatar

Represent a person or team with an image or initials.

On this page

Use this component from the local workspace package. Follow the setup guide first. A public npm package and registry are not available yet.

View registry files

Import the component into your page or component file.

Usage
import { Avatar } from "@dethink/components";

export function Example() {
  return <Avatar name="Noah Reyes" src="/avatars/noah.png" ring="border" />;
}

Try the examples, then open the code to use them in your app.

Identity rows

Use a real image when available, then fall back to generated initials without changing the surrounding layout.

Noah Reyes

Noah Reyes

Owner

Design systems

Mira Patel

Mira Patel

Active

Data operations

June Okafor

Fallback

Identity falls back to initials

Show sourceexamples/avatar/basic.tsx
examples/avatar/basic.tsx
"use client";

import { Avatar, Badge } from "@dethink/components";
import { avatarImagePaths } from "@/examples/_shared/avatar-members";

const people = [
  {
    image: avatarImagePaths.noah,
    meta: "Design systems",
    name: "Noah Reyes",
    status: "Owner",
    tone: "primary" as const,
  },
  {
    image: avatarImagePaths.mira,
    meta: "Data operations",
    name: "Mira Patel",
    status: "Active",
    tone: "success" as const,
  },
  {
    image: undefined,
    meta: "Identity falls back to initials",
    name: "June Okafor",
    status: "Fallback",
    tone: "info" as const,
  },
];

export function AvatarBasic() {
  return (
    <div className="grid w-full max-w-md gap-4">
      {people.map((person) => (
        <div
          key={person.name}
          className="flex min-w-0 items-center gap-[var(--dt-space-3)]"
        >
          <Avatar
            name={person.name}
            src={person.image}
            tone={person.tone}
            ring="border"
            size="lg"
            decoding="async"
            loading="lazy"
            sizes="48px"
          />
          <div className="min-w-0 flex-1">
            <div className="flex min-w-0 flex-wrap items-center gap-2">
              <p className="truncate text-sm font-medium">{person.name}</p>
              <Badge size="xs" tone={person.tone}>
                {person.status}
              </Badge>
            </div>
            <p className="text-muted-foreground truncate text-xs">
              {person.meta}
            </p>
          </div>
        </div>
      ))}
    </div>
  );
}

Sizes, shapes, and rings

Size follows density tokens, shape controls clipping, and ring adds separation on layered surfaces.

Avatar size xsxs
Avatar size smsm
Avatar size mdmd
Avatar size lglg
Avatar size xlxl
Avatar size 2xl2xl
circle avatarcircle
rounded avatarrounded
square avatarsquare
none ring avatarnone
border ring avatarborder
ring ring avatarring
Show sourceexamples/avatar/sizes.tsx
examples/avatar/sizes.tsx
"use client";

import {
  Avatar,
  type AvatarRing,
  type AvatarShape,
  type AvatarSize,
} from "@dethink/components";
import { avatarImagePaths } from "@/examples/_shared/avatar-members";

const sizes: AvatarSize[] = ["xs", "sm", "md", "lg", "xl", "2xl"];
const shapes: AvatarShape[] = ["circle", "rounded", "square"];
const rings: AvatarRing[] = ["none", "border", "ring"];

export function AvatarSizes() {
  return (
    <div className="grid w-full gap-8">
      <div className="flex flex-wrap items-end gap-[var(--dt-space-4)]">
        {sizes.map((size) => (
          <div key={size} className="grid justify-items-center gap-2">
            <Avatar
              name={`Avatar size ${size}`}
              src={avatarImagePaths.eli}
              size={size}
              ring="border"
              decoding="async"
              loading="lazy"
              sizes="72px"
            />
            <span className="text-muted-foreground text-xs">{size}</span>
          </div>
        ))}
      </div>
      <div className="grid gap-4 sm:grid-cols-3">
        {shapes.map((shape) => (
          <div
            key={shape}
            className="flex min-w-0 items-center gap-[var(--dt-space-3)]"
          >
            <Avatar
              name={`${shape} avatar`}
              src={avatarImagePaths.mira}
              shape={shape}
              size="lg"
              ring="border"
              decoding="async"
              loading="lazy"
              sizes="48px"
            />
            <span className="truncate text-sm font-medium capitalize">
              {shape}
            </span>
          </div>
        ))}
      </div>
      <div className="flex flex-wrap items-center gap-[var(--dt-space-4)]">
        {rings.map((ring) => (
          <div key={ring} className="grid justify-items-center gap-2">
            <Avatar
              name={`${ring} ring avatar`}
              src={avatarImagePaths.leo}
              ring={ring}
              size="xl"
              decoding="async"
              loading="lazy"
              sizes="64px"
            />
            <span className="text-muted-foreground text-xs">{ring}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

Fallback states

Avatar handles loaded images, generated initials, icon identities, organization shapes, and failed image sources.

Mira Patel

Image loaded

Accessible image alt

Generated initials

Name-derived fallback

Icon fallback

System identity

Organization

Square brand shape

Missing profile

Failed image fallback

onError

The image source intentionally fails and returns to fallback content.

Show sourceexamples/avatar/states.tsx
examples/avatar/states.tsx
"use client";

import { Building2, Bot, UserRound } from "lucide-react";
import { Avatar, Badge } from "@dethink/components";
import { avatarImagePaths } from "@/examples/_shared/avatar-members";

export function AvatarStates() {
  return (
    <div className="grid w-full gap-4 sm:grid-cols-2">
      <div className="flex items-center gap-[var(--dt-space-3)]">
        <Avatar
          name="Mira Patel"
          src={avatarImagePaths.mira}
          ring="ring"
          size="lg"
          decoding="async"
          loading="lazy"
          sizes="48px"
        />
        <div>
          <p className="text-sm font-medium">Image loaded</p>
          <p className="text-muted-foreground text-xs">Accessible image alt</p>
        </div>
      </div>
      <div className="flex items-center gap-[var(--dt-space-3)]">
        <Avatar name="Sam Rivera" tone="warning" ring="border" size="lg" />
        <div>
          <p className="text-sm font-medium">Generated initials</p>
          <p className="text-muted-foreground text-xs">Name-derived fallback</p>
        </div>
      </div>
      <div className="flex items-center gap-[var(--dt-space-3)]">
        <Avatar
          fallbackIcon={<Bot />}
          name="AI reviewer"
          tone="info"
          shape="rounded"
          ring="border"
          size="lg"
        />
        <div>
          <p className="text-sm font-medium">Icon fallback</p>
          <p className="text-muted-foreground text-xs">System identity</p>
        </div>
      </div>
      <div className="flex items-center gap-[var(--dt-space-3)]">
        <Avatar
          fallbackIcon={<Building2 />}
          name="Northstar Audit"
          shape="square"
          tone="success"
          ring="border"
          size="lg"
        />
        <div>
          <p className="text-sm font-medium">Organization</p>
          <p className="text-muted-foreground text-xs">Square brand shape</p>
        </div>
      </div>
      <div className="flex items-center gap-[var(--dt-space-3)] sm:col-span-2">
        <Avatar
          fallbackIcon={<UserRound />}
          name="Missing profile"
          src="/avatars/showcase/missing-profile.png"
          loading="lazy"
          tone="destructive"
          ring="border"
          size="lg"
        />
        <div className="min-w-0">
          <div className="flex flex-wrap items-center gap-2">
            <p className="text-sm font-medium">Failed image fallback</p>
            <Badge size="xs" tone="destructive" variant="outline">
              onError
            </Badge>
          </div>
          <p className="text-muted-foreground text-xs">
            The image source intentionally fails and returns to fallback
            content.
          </p>
        </div>
      </div>
    </div>
  );
}

Profile list recipe

A production row pairs Avatar with Badge and recency metadata for review and access workflows.

Access review

Identity rows pair avatars with status metadata and compact badges.

Noah Reyes

Noah Reyes

Design systems

Verified2 min ago
Mira Patel

Mira Patel

Release operations

Verified15 min ago
Leo Novak

Leo Novak

Product lead

Verified1 hr ago
Show sourceexamples/avatar/recipe-profile-row.tsx
examples/avatar/recipe-profile-row.tsx
"use client";

import { Clock3, ShieldCheck } from "lucide-react";
import {
  Avatar,
  Badge,
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@dethink/components";
import { avatarImagePaths } from "@/examples/_shared/avatar-members";

const reviewers = [
  {
    image: avatarImagePaths.noah,
    lastSeen: "2 min ago",
    name: "Noah Reyes",
    role: "Design systems",
    tone: "primary" as const,
  },
  {
    image: avatarImagePaths.mira,
    lastSeen: "15 min ago",
    name: "Mira Patel",
    role: "Release operations",
    tone: "success" as const,
  },
  {
    image: avatarImagePaths.leo,
    lastSeen: "1 hr ago",
    name: "Leo Novak",
    role: "Product lead",
    tone: "warning" as const,
  },
];

export function AvatarProfileRow() {
  return (
    <Card className="w-full max-w-lg">
      <CardHeader>
        <CardTitle>Access review</CardTitle>
        <CardDescription>
          Identity rows pair avatars with status metadata and compact badges.
        </CardDescription>
      </CardHeader>
      <CardContent className="grid gap-4">
        {reviewers.map((reviewer) => (
          <div
            key={reviewer.name}
            className="flex min-w-0 items-center justify-between gap-[var(--dt-space-4)]"
          >
            <div className="flex min-w-0 items-center gap-[var(--dt-space-3)]">
              <Avatar
                name={reviewer.name}
                src={reviewer.image}
                tone={reviewer.tone}
                ring="border"
                size="lg"
                decoding="async"
                loading="lazy"
                sizes="48px"
              />
              <div className="min-w-0">
                <p className="truncate text-sm font-medium">{reviewer.name}</p>
                <p className="text-muted-foreground truncate text-xs">
                  {reviewer.role}
                </p>
              </div>
            </div>
            <div className="flex shrink-0 flex-col items-end gap-1">
              <Badge icon={<ShieldCheck />} size="xs" tone={reviewer.tone}>
                Verified
              </Badge>
              <span className="text-muted-foreground inline-flex items-center gap-1 text-xs">
                <Clock3 aria-hidden="true" className="size-3" />
                {reviewer.lastSeen}
              </span>
            </div>
          </div>
        ))}
      </CardContent>
    </Card>
  );
}

AvatarProps extends span attributes while owning image accessibility and layout-sensitive image props.

Avatar props
PropWhat it doesDefault
namestringAccessible label and source for generated initials when explicit initials are not provided.Not set
srcstringImage URL rendered inside the avatar until it fails, then fallback content is shown.Not set
initialsstringExplicit fallback initials, normalized to at most three uppercase characters.Not set
fallbackIconReactNodeIcon rendered when no image or initials are available, useful for teams and systems.Not set
size"xs" | "sm" | "md" | "lg" | "xl" | "2xl"Density-aware avatar size."md"
shape"circle" | "rounded" | "square"Avatar clipping shape."circle"
tone"neutral" | "primary" | "success" | "warning" | "destructive" | "info"Fallback background and foreground tone."neutral"
ring"none" | "border" | "ring"Optional edge treatment for separation on busy surfaces."none"
motion"none" | "subtle" | "standard"Hover and focus feedback, reduced automatically for users who prefer less motion."standard"
decorativebooleanMarks the avatar as presentation-only when surrounding text already provides the identity.false
imagePropsAvatarImagePropsSafe pass-through props for the internal image, excluding layout and accessibility props owned by Avatar.Not set