๐ŸŸข Avatar


Basic

NAvatar - used to display a user's profile picture, initials, or icon.

PRPR
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar label="PR" />

    <NAvatar src="/images/avatar.png" alt="Phojie Rengel" />
  </div>
</template>

Variants

variant="{variant}" - change the variant of the avatar.

VariantDescription
solidThe default variant.
softThe soft variant.
outlineThe outline variant.
~The unstyle or base variant
PRPRPRPR
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar avatar="solid" label="PR" />

    <NAvatar avatar="soft" label="PR" />

    <NAvatar avatar="outline" label="PR" />

    <NAvatar avatar="~" label="PR" />
  </div>
</template>

Color

avatar="{variant}-{color}" - change the color of the avatar.

You can use any color provided by the Tailwind CSS color palette, the default is primary. You can also add your own colors to the palette through the Configuration section.
Dynamic colors:
PRPRPRPRPRPR

Static colors:
PRPR
<template>
  <div flex="~ col" gap-4>
    <span class="text-sm font-medium">Dynamic colors:</span>
    <div flex="~ items-center" gap-4>
      <NAvatar avatar="solid-gray" label="PR" />

      <NAvatar avatar="solid-primary" label="PR" />

      <NAvatar avatar="soft-error" label="PR" />

      <NAvatar avatar="soft-info" label="PR" />

      <NAvatar avatar="outline-purple" label="PR" />

      <NAvatar avatar="outline-pink" label="PR" />
    </div>

    <hr border="base">

    <span class="text-sm font-medium">Static colors:</span>

    <div flex="~ items-center" gap-4>
      <NAvatar avatar="solid-black" label="PR" />

      <NAvatar avatar="solid-white" label="PR" />
    </div>
  </div>
</template>

Icon

icon="{icon}" - set the icon of the avatar, instead of an image.

You can't use the icon prop with the src prop.
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar icon="i-heroicons-bookmark-solid" />

    <NAvatar icon="i-heroicons-bell-alert-20-solid" />
  </div>
</template>

Skeleton mode

skeleton - set skeleton mode while the image is loading.

The skeleton prop is only available when using the src prop.
<template>
  <NAvatar
    src="/images/avatar.png"
    skeleton
  />
</template>

With Indicator

Refer to the Indicator component for more details.

PR
PR
11
PRNewNew
<template>
  <div flex="~ items-center" gap-4>
    <NIndicator indicator="solid-green">
      <NAvatar src="/images/avatar.png" alt="Phojie Rengel" />
    </NIndicator>

    <NIndicator indicator="solid-gray bottom-right">
      <NAvatar src="/images/avatar.png" alt="Phojie Rengel" />
    </NIndicator>

    <NIndicator label="1" indicator="solid-red top-right">
      <NAvatar icon="i-heroicons-phone-arrow-down-left-20-solid" />
    </NIndicator>

    <NIndicator indicator="solid-info" label="New">
      <NAvatar src="/images/avatar.png" alt="Phojie Rengel" />
    </NIndicator>
  </div>
</template>

Size

size="{size}" - change the size of the avatar.

๐Ÿš€ You can freely adjust the size of the avatar using any size imaginable. No limits exist, and you can use breakpoints such as sm:sm, xs:lg to change size based on screen size or states such as hover:lg, focus:3xl to change size based on input state and more.

The padding and font-size of the avatar scale depends on the size. If you want to change the font-size and padding simultaneously, you can always customize it using utility classes.
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar
      size="xs"
      src="/images/avatar.png"
    />

    <NAvatar
      size="lg"
      src="/images/avatar.png"
    />

    <NAvatar
      size="2xl"
      src="/images/avatar.png"
    />

    <NAvatar
      size="39px"
      src="/images/avatar.png"
    />

    <NAvatar
      size="sm:3xl md:4xl lg:5xl"
      src="/images/avatar.png"
    />
  </div>
</template>

Fallbacks

fallback="{fallback}" - set the fallback image url.

If there is an error loading the avatar image, the component falls back to an alternative in the following order:

  1. the provided fallback slot or prop
  2. the provided label slot or prop
  3. the first two letters of each word in the alt prop
  4. a generic avatar icon
avatarDSDS
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar
      src="https://test.com/dummy.png"
      fallback="/images/avatar.png"
    />

    <NAvatar
      src="https://test.com/dummy.png"
      label="DS"
    />

    <NAvatar
      src="https://test.com/dummy.png"
      alt="Dummy Src"
    />

    <NAvatar
      src="https://test.com/dummy.png"
    />
  </div>
</template>

Lazy loading

Use the Lazy prefix provided by nuxt to lazy load the NAvatar component. For example:

<LazyNAvatar src="..." />

Customization

You can customize the avatar using the una prop and utility classes.

You can also globally customize the avatar preset if you want to have a different default style. See Configuration section for more details.
PRPR
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar label="PR" size="lg" avatar="~" style="background-color:#303" class="text-white" />

    <NAvatar avatar="solid-white" label="PR" size="lg" class="rounded-md text-$c-brand-next ring-$c-brand-light" />

    <NAvatar size="2xl" class="rounded-none" src="/images/avatar.png" />
  </div>
</template>

Slots

SlotDescription
defaultThe image, icon, or labels of the avatar.
Example 1
You will lose the ability to have fallbacks if you use the default slot, we usually recommend this if you want to have a component inside the avatar such as NIcon or just a label. But if you want to have a fallback, you can see the example 2 below.
PR
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar avatar="solid-primary" class="rounded-md">
      <span class="font-extrabold text-white">PR</span>
    </NAvatar>

    <NAvatar avatar="soft-primary">
      <NIcon name="i-heroicons-envelope-20-solid" />
    </NAvatar>
  </div>
</template>

PropDescription
isLoadingThe loading state of the avatar.
errorThe error state of the avatar.
isReadyThe ready state of the avatar.
srcThe source of the avatar.
altThe alt text of the avatar.
Example 2
You don't have to use this case, this is just an example of how you can use the default slot to have fallbacks in a custom way.
PRloading
<template>
  <div flex="~ items-center" gap-4>
    <NAvatar label="PR">
      <template #default="props">
        <span>{{ props.label }}</span>
      </template>
    </NAvatar>

    <NAvatar src="/images/avatar.png" alt="Phojie Rengel">
      <template #default="slot">
        <!-- if ready -->
        <img v-if="slot.isReady && !slot.error" :src="slot.src" :alt="slot.alt">

        <!-- if loading -->
        <span v-else-if="slot.isLoading">loading</span>

        <!-- fallbacks -->
        <span v-else-if="slot.error">error</span>
      </template>
    </NAvatar>
  </div>
</template>

Props

export interface NAvatarProps {
  /**
   * Allows you to add `UnaUI` avatar preset properties,
   * Think of it as a shortcut for adding options or variants to the preset if available.
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/avatar.ts
   * @example
   * avatar="solid-green"
   */
  avatar?: string
  /**
   * Add icon instead of image.
   *
   * @example
   * icon="i-heroicons-information-circle"
   */
  icon?: string
  /**
   * Add a label to the avatar.
   *
   * @example
   * label="PR"
   */
  label?: string
  /**
   * Set the size of the avatar.
   */
  size?: string
  /**
   * Add src of the image.
   *
   * @example
   * src="https://i.pravatar.cc/300"
   */
  src?: string
  /**
   * Add alt of the image.
   *
   * @example
   * alt="Profile"
   */
  alt?: string
  /**
   * Add fallback of the image.
   *
   * @example
   * fallback="https://i.pravatar.cc/300"
   */
  fallback?: string

  /**
   * Add a delay before showing the avatar.
   *
   * @default 0
   */
  delay?: number

  /**
   * Add a skeleton effect to the avatar.
   *
   * @default false
   */
  skeleton?: boolean

  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/avatar.ts
   */
  una?: {
    // base
    avatar?: string
    avatarLabel?: string
    avatarSrc?: string
    avatarFallback?: string
    avatarIconBase?: string

    // icons
    avatarFallbackIcon?: string
  }
}

Presets

type AvatarPrefix = 'avatar'

export const staticAvatar: Record<`${AvatarPrefix}-${string}` | AvatarPrefix, string> = {
  // config
  'avatar-default-variant': 'avatar-solid',
  'avatar-fallback-icon': 'i-heroicons-user-20-solid',

  // base
  'avatar': 'relative font-medium leading-none h-2.5em w-2.5em inline-flex items-center justify-center rounded-full overflow-hidden',
  'avatar-label': '',
  'avatar-fallback': '',
  'avatar-fallback-icon-base': 'text-1.5em',
  'avatar-src': 'w-full h-full',
  'avatar-icon-base': 'text-1.2em',

  // variants
  'avatar-solid-white': 'bg-base text-base ring-1 ring-base',
  'avatar-solid-black': 'bg-inverted text-inverted',
}

export const dynamicAvatar: [RegExp, (params: RegExpExecArray) => string][] = [
  // variants
  [/^avatar-solid(-(\S+))?$/, ([, , c = 'gray']) => `bg-${c}-600 dark:bg-${c}-500 text-inverted`],
  [/^avatar-soft(-(\S+))?$/, ([, , c = 'gray']) => `bg-${c}-50 text-${c}-700 dark:text-${c}-400 dark:bg-${c}-900`],
  [/^avatar-outline(-(\S+))?$/, ([, , c = 'gray']) => `bg-transparent text-${c}-500 dark:text-${c}-400 ring-1 ring-${c}-500 dark:ring-${c}-400`],
]

export const avatar = [
  ...dynamicAvatar,
  staticAvatar,
]

Component

<script setup lang="ts">
import { useImage } from '@vueuse/core'
import { computed } from 'vue'
import type { NAvatarProps } from '../../types'
import NIcon from './Icon.vue'

const props = withDefaults(defineProps<NAvatarProps>(), {
  delay: 0,
})

const { isLoading, error, isReady } = useImage({ src: props?.src ?? '' }, { delay: props.delay })

// TODO: sync with NAvatarProps
const avatarVariants = ['solid', 'soft', 'outline'] as const
const hasVariant = computed(() => avatarVariants.some(avatarVariants => props.avatar?.includes(avatarVariants)))
const isBaseVariant = computed(() => props.avatar?.includes('~'))

const placeholder = computed(() => {
  if (props.label)
    return props.label

  return props.alt?.split(' ').map(word => word[0]).join('').slice(0, 2)
})
</script>

<template>
  <span
    :avatar="avatar"
    :size="size"
    class="avatar"
    :class="[
      { 'avatar-default-variant': !hasVariant && !isBaseVariant },
      { 'animate-pulse': isLoading && skeleton && src },
      una?.avatar,
    ]"
  >
    <template v-if="!icon">
      <slot
        v-if="!skeleton"
        v-bind="{ isLoading, error, isReady, ...props }"
      >
        <img
          v-if="isReady && !error"
          avatar="src"
          :src="src"
          :alt="alt"
          :class="una?.avatarSrc"
        >

        <img
          v-else-if="fallback"
          avatar="src"
          :src="fallback"
          :alt="alt ?? 'avatar'"
          :class="una?.avatarFallback"
        >

        <span
          v-else-if="placeholder"
          avatar="label"
          :class="una?.avatarLabel"
        >
          {{ placeholder }}
        </span>

        <NIcon
          v-else
          avatar="fallback-icon-base"
          :name="una?.avatarFallbackIcon ?? 'avatar-fallback-icon'"
          :class="una?.avatarFallbackIcon"
        />
      </slot>
    </template>

    <template v-else>
      <NIcon
        avatar="icon-base"
        :class="una?.avatarIconBase"
        :name="icon"
      />
    </template>
  </span>
</template>