Avatar ​

Shows a person or a workspace as a photo, or as the first letter of their name when there is no photo.

J
label
image
size
shape
theme
<Avatar
  label="John Doe"
/>

Examples ​

Comment thread ​

A photo when the person has one, and their initial in a theme color when they do not.

Jane Cooper
Jane Cooper2h

Pushed the new pricing table. Can someone check the mobile layout?

S
Sara Khan1h

Looks good on my phone. The last column wraps a little early.

Rahul Mehta
Rahul Mehta12m

Fixed the wrap. Merging after lunch.

vue
<script setup lang="ts">
import { Avatar } from 'frappe-ui'

const avatarFor = (seed: string) => `https://i.pravatar.cc/80?u=${seed}`

// Sara has no photo, so her avatar falls back to her initial.
const comments = [
  {
    author: 'Jane Cooper',
    image: avatarFor('[email protected]'),
    time: '2h',
    text: 'Pushed the new pricing table. Can someone check the mobile layout?',
  },
  {
    author: 'Sara Khan',
    image: '',
    theme: 'violet' as const,
    time: '1h',
    text: 'Looks good on my phone. The last column wraps a little early.',
  },
  {
    author: 'Rahul Mehta',
    image: avatarFor('[email protected]'),
    time: '12m',
    text: 'Fixed the wrap. Merging after lunch.',
  },
]
</script>

<template>
  <div class="flex w-full max-w-md flex-col gap-4">
    <div v-for="c in comments" :key="c.author" class="flex gap-2.5">
      <Avatar :image="c.image" :label="c.author" :theme="c.theme" size="lg" />
      <div class="flex flex-col gap-1">
        <div class="flex items-baseline gap-1.5">
          <span class="text-base font-medium text-ink-gray-9">{{
            c.author
          }}</span>
          <span class="text-sm text-ink-gray-5">{{ c.time }}</span>
        </div>
        <p class="text-p-base text-ink-gray-7">{{ c.text }}</p>
      </div>
    </div>
  </div>
</template>

Assignees ​

Overlapping avatars on a task row. A ring in the page color separates each avatar from the one under it, and a count stands in for the rest.

Redesign the invoice page
Jane Cooper
Rahul Mehta
Migrate reports to the new API
S
Leo Park
Mia Wong
+2
Write release notes
Mia Wong
vue
<script setup lang="ts">
import { Avatar } from 'frappe-ui'

const avatarFor = (seed: string) => `https://i.pravatar.cc/80?u=${seed}`

const people = {
  jane: { label: 'Jane Cooper', image: avatarFor('[email protected]') },
  rahul: { label: 'Rahul Mehta', image: avatarFor('[email protected]') },
  sara: { label: 'Sara Khan', image: '' },
  leo: { label: 'Leo Park', image: avatarFor('[email protected]') },
  mia: { label: 'Mia Wong', image: avatarFor('[email protected]') },
}

const tasks = [
  {
    title: 'Redesign the invoice page',
    assignees: [people.jane, people.rahul],
  },
  {
    title: 'Migrate reports to the new API',
    assignees: [people.sara, people.leo, people.mia, people.jane, people.rahul],
  },
  { title: 'Write release notes', assignees: [people.mia] },
]

const shown = 3

// A ring in the page color cuts each avatar out of the one under it.
const cutout = { boxShadow: '0 0 0 2px var(--surface-base)' }
</script>

<template>
  <div class="flex w-full max-w-md flex-col divide-y divide-outline-gray-1">
    <div
      v-for="task in tasks"
      :key="task.title"
      class="flex items-center gap-3 py-2.5"
    >
      <span class="lucide-circle size-4 text-ink-gray-4" aria-hidden="true" />
      <span class="flex-1 truncate text-base text-ink-gray-8">
        {{ task.title }}
      </span>
      <div class="flex -space-x-1.5">
        <Avatar
          v-for="person in task.assignees.slice(0, shown)"
          :key="person.label"
          :image="person.image"
          :label="person.label"
          size="lg"
          :style="cutout"
        />
        <span
          v-if="task.assignees.length > shown"
          class="grid size-7 place-items-center rounded-full bg-surface-gray-2 text-xs font-medium text-ink-gray-6"
          :style="cutout"
        >
          +{{ task.assignees.length - shown }}
        </span>
      </div>
    </div>
  </div>
</template>

Online status ​

The #indicator slot places a dot on the bottom-right corner.

Jane Cooper
Jane Cooperonline
Rahul Mehta
Rahul Mehtaaway
S
Sara Khanoffline
vue
<script setup lang="ts">
import { Avatar } from 'frappe-ui'

const avatarFor = (seed: string) => `https://i.pravatar.cc/80?u=${seed}`

const dot = {
  online: 'bg-surface-green-5',
  away: 'bg-surface-amber-5',
  offline: 'bg-surface-gray-4',
}

const members = [
  {
    name: 'Jane Cooper',
    status: 'online' as const,
    image: avatarFor('[email protected]'),
  },
  {
    name: 'Rahul Mehta',
    status: 'away' as const,
    image: avatarFor('[email protected]'),
  },
  { name: 'Sara Khan', status: 'offline' as const, image: '' },
]
</script>

<template>
  <div class="flex w-60 flex-col gap-1">
    <div
      v-for="m in members"
      :key="m.name"
      class="flex items-center gap-2 rounded px-2 py-1.5 hover:bg-surface-gray-2"
    >
      <Avatar :image="m.image" :label="m.name" size="lg">
        <template #indicator>
          <span class="block size-full rounded-full" :class="dot[m.status]" />
        </template>
      </Avatar>
      <span class="flex-1 text-base text-ink-gray-8">{{ m.name }}</span>
      <span class="text-sm capitalize text-ink-gray-5">{{ m.status }}</span>
    </div>
  </div>
</template>

Workspace switcher ​

Square avatars stand for workspaces and teams, round ones for people. The default slot replaces the initial with any content, here an icon.

vue
<script setup lang="ts">
import { Avatar } from 'frappe-ui'
import type { AvatarTheme } from 'frappe-ui'

// Square avatars stand for places, round ones for people.
const workspaces: { name: string; members: number; theme: AvatarTheme }[] = [
  { name: 'Acme Design', members: 12, theme: 'blue' },
  { name: 'Growth', members: 5, theme: 'green' },
  { name: 'Support', members: 8, theme: 'amber' },
]
</script>

<template>
  <div
    class="flex w-60 flex-col gap-1 rounded-lg bg-surface-elevation-2 p-1.5 shadow-xl ring-1 ring-black/5"
  >
    <button
      v-for="(w, i) in workspaces"
      :key="w.name"
      type="button"
      class="flex items-center gap-2 rounded px-2 py-1.5 text-left hover:bg-surface-gray-2"
    >
      <Avatar :label="w.name" :theme="w.theme" shape="square" size="xl" />
      <span class="flex flex-1 flex-col">
        <span class="text-base font-medium text-ink-gray-8">{{ w.name }}</span>
        <span class="text-sm text-ink-gray-5">{{ w.members }} members</span>
      </span>
      <span
        v-if="i === 0"
        class="lucide-check size-4 text-ink-gray-7"
        aria-hidden="true"
      />
    </button>
    <div class="my-1 border-t border-outline-gray-1" />
    <button
      type="button"
      class="flex items-center gap-2 rounded px-2 py-1.5 text-left hover:bg-surface-gray-2"
    >
      <!-- The default slot replaces the initial, here with an icon. -->
      <Avatar shape="square" size="xl">
        <span class="lucide-plus size-full" aria-hidden="true" />
      </Avatar>
      <span class="text-base text-ink-gray-8">New workspace</span>
    </button>
  </div>
</template>

Profile header ​

A size-* class sets a size beyond the largest size step.

Jane Cooper
Jane CooperProduct designer · Bengaluru
vue
<script setup lang="ts">
import { Avatar, Button } from 'frappe-ui'
</script>

<template>
  <div class="flex w-full max-w-md items-center gap-4">
    <!-- A size-* class goes past the largest size step. -->
    <Avatar
      image="https://i.pravatar.cc/[email protected]"
      label="Jane Cooper"
      class="size-16"
    />
    <div class="flex flex-1 flex-col gap-1">
      <span class="text-xl font-semibold text-ink-gray-9">Jane Cooper</span>
      <span class="text-base text-ink-gray-6"
        >Product designer · Bengaluru</span
      >
    </div>
    <Button label="Message" icon-left="lucide-message-circle" />
  </div>
</template>

Behavior ​

Fallback ​

The avatar shows the initial when image is empty or the image fails to load. The initial is the first character of label, uppercased, so pass the full name rather than initials: label="Jane Cooper" shows J. theme colors the initial and its background. It has no effect on a photo.

Custom sizes ​

A sizing class on the avatar (size-*, w-*, h-*) replaces the size prop's width and height. The size prop still sets the text size of the initial and the size of the indicator dot. A responsive class like sm:size-16 works alongside size: size sets the width on small screens, and the class takes over from its breakpoint up.

Accessibility ​

The image uses label as its alt text.

API Reference ​

Show types
typescript
export type AvatarTheme =
  | 'gray'
  | 'blue'
  | 'green'
  | 'amber'
  | 'red'
  | 'violet'

export interface AvatarProps {
  /** Image URL used for the avatar */
  image?: string

  /** Fallback text shown when the image is missing */
  label?: string

  /** Controls the overall size of the avatar */
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'

  /** Defines the avatar shape */
  shape?: 'circle' | 'square'

  /** Visual color theme used for the fallback avatar */
  theme?: AvatarTheme
}
image
string

Image URL used for the avatar

label
string

Fallback text shown when the image is missing

size
= "md"
"xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"

Controls the overall size of the avatar

shape
= "circle"
"circle" | "square"

Defines the avatar shape

theme
= "gray"
AvatarTheme

Visual color theme used for the fallback avatar

default
—

Custom content shown inside the avatar (icon / initials replacement)

indicator
—

Small indicator shown at the bottom-right of the avatar