Avatar
Vue data-displayRound or rounded-square user image with a fallback that shows initials or an icon when the image is missing or fails to load. Sizes from xs to 2xl, optional status dot, and a group composition for stacked avatar lists.
Also available for React ->Installation
$ pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/avatar.json$ npx shadcn-vue@latest add https://uipkge.dev/r/vue/avatar.json$ yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/avatar.json$ bunx shadcn-vue@latest add https://uipkge.dev/r/vue/avatar.json
Or with the named registry:
npx shadcn-vue@latest add @uipkge/avatar
Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
size | 'xs''sm''default''lg''xl''2xl' | — | optional |
rounded | 'none''sm''default''md''lg''xl''2xl''3xl''full' | — | optional |
color | 'default''primary''secondary''destructive''success''warning''info''error''muted' | — | optional |
variant | 'default''outlined''soft' | — | optional |
tile | boolean | false | optional |
disabled | boolean | false | optional |
loading | boolean | false | optional |
Dependencies
Used by
Files (6)
-
app/components/ui/avatar/Avatar.vue 1.6 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { computed } from 'vue' import { cn } from '@/lib/utils' import { avatarVariants } from './avatar.variants' // Inlined unions: SFC compiler can't extract runtime props from // `AvatarVariants['size']` etc. indexed-access types. const props = withDefaults( defineProps<{ class?: HTMLAttributes['class'] size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl' rounded?: 'none' | 'sm' | 'default' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full' color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted' variant?: 'default' | 'outlined' | 'soft' tile?: boolean disabled?: boolean loading?: boolean }>(), { tile: false, disabled: false, loading: false, }, ) const emit = defineEmits<{ click: [event: MouseEvent] error: [event: Event] load: [event: Event] }>() function handleClick(event: MouseEvent) { if (!props.disabled) { emit('click', event) } } function handleError(event: Event) { emit('error', event) } function handleLoad(event: Event) { emit('load', event) } const rootClasses = computed(() => cn( avatarVariants({ size: props.size, rounded: props.rounded, color: props.color, variant: props.variant }), props.tile ? 'rounded-none' : '', props.disabled ? 'opacity-50 cursor-not-allowed' : '', props.loading ? 'animate-pulse' : '', props.class, ), ) </script> <template> <span :class="rootClasses" data-uipkge data-slot="avatar" @click="handleClick"> <slot /> </span> </template> -
app/components/ui/avatar/AvatarFallback.vue 1 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { computed } from 'vue' import { cn } from '@/lib/utils' import { avatarFallbackVariants } from './avatar.variants' // Inlined unions: SFC compiler can't extract runtime props from // indexed-access types. const props = withDefaults( defineProps<{ class?: HTMLAttributes['class'] size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl' color?: 'default' | 'primary' | 'secondary' | 'destructive' | 'success' | 'warning' | 'info' | 'error' | 'muted' text?: string }>(), { size: 'default', color: 'default', }, ) const emit = defineEmits<{ click: [event: MouseEvent] }>() function handleClick(event: MouseEvent) { emit('click', event) } const rootClasses = computed(() => cn(avatarFallbackVariants({ size: props.size, color: props.color }), props.class)) </script> <template> <span :class="rootClasses" data-uipkge data-slot="avatar-fallback" @click="handleClick"> <template v-if="text">{{ text }}</template> <slot v-else /> </span> </template> -
app/components/ui/avatar/AvatarGroup.vue 1.7 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { cn } from '@/lib/utils' export interface AvatarGroupProps { class?: HTMLAttributes['class'] max?: number overlap?: boolean size?: 'xs' | 'sm' | 'default' | 'lg' | 'xl' | '2xl' total?: number } const props = withDefaults(defineProps<AvatarGroupProps>(), { overlap: true, size: 'default', }) const emit = defineEmits<{ click: [event: MouseEvent] }>() function handleClick(event: MouseEvent) { emit('click', event) } </script> <template> <div :class="cn('flex items-center', overlap ? '-space-x-2' : 'gap-1', props.class)" data-uipkge data-slot="avatar-group" @click="handleClick" > <slot :max="max" :size="size" /> <template v-if="max && ($slots.default?.()?.length || 0) > max"> <div :class=" cn( 'bg-muted ring-background relative flex shrink-0 overflow-hidden rounded-full ring-2', size === 'xs' ? 'size-4 text-[8px]' : size === 'sm' ? 'size-6 text-xs' : size === 'default' ? 'size-8 text-sm' : size === 'lg' ? 'size-12 text-base' : size === 'xl' ? 'size-16 text-lg' : 'size-20 text-xl', ) " > <slot name="overflow" :count="($slots.default?.()?.length || 0) - max + 1" /> <span v-if="!$slots['overflow']" class="flex size-full items-center justify-center font-medium"> +{{ ($slots.default?.()?.length || 0) - max + 1 }} </span> </div> </template> </div> </template> -
app/components/ui/avatar/AvatarImage.vue 1.7 kB
<script setup lang="ts"> import type { HTMLAttributes } from 'vue' import { ref, watch } from 'vue' import { cn } from '@/lib/utils' const props = withDefaults( defineProps<{ class?: HTMLAttributes['class'] src?: string alt?: string fallback?: string loading?: 'eager' | 'lazy' referrerpolicy?: | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url' | '' crossorigin?: 'anonymous' | 'use-credentials' onerror?: (event: Event) => void onload?: (event: Event) => void }>(), { loading: 'lazy', }, ) const emit = defineEmits<{ error: [event: Event] load: [event: Event] }>() // Track <img> failures so a broken URL falls back to the slot // (consumer renders AvatarFallback there). Without this, an HTTP 404 // leaves the broken-image glyph forever, which contradicts the docs // promise that AvatarFallback shows when the image fails. const errored = ref(false) watch( () => props.src, () => { errored.value = false }, ) function handleError(event: Event) { errored.value = true emit('error', event) if (props.onerror) { props.onerror(event) } } function handleLoad(event: Event) { emit('load', event) if (props.onload) { props.onload(event) } } </script> <template> <img v-if="src && !errored" :src="src" :alt="alt" :loading="loading" :referrerpolicy="referrerpolicy" :crossorigin="crossorigin" :class="cn('aspect-square size-full object-cover', props.class)" data-uipkge data-slot="avatar-image" @error="handleError" @load="handleLoad" /> <slot v-else /> </template> -
app/components/ui/avatar/avatar.variants.ts 3.4 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' /** * Variant definitions live in their own file (rather than the package * `index.ts` or inline in the SFC) so `Avatar.vue` / `AvatarFallback.vue` * can `import { avatarVariants } from './avatar.variants'` without * creating a circular dependency back through the index. The circular * form caused intermittent `$setup.avatarVariants is not a function` * errors during dev SSR. */ export const avatarVariants = cva('relative flex shrink-0 overflow-hidden', { variants: { size: { xs: 'size-4', sm: 'size-6', default: 'size-8', lg: 'size-12', xl: 'size-16', '2xl': 'size-20', }, rounded: { none: 'rounded-none', sm: 'rounded-sm', default: 'rounded-full', md: 'rounded-md', lg: 'rounded-lg', xl: 'rounded-xl', '2xl': 'rounded-2xl', '3xl': 'rounded-3xl', full: 'rounded-full', }, color: { default: '', primary: 'bg-primary text-primary-foreground', secondary: 'bg-secondary text-secondary-foreground', destructive: 'bg-destructive text-destructive-foreground', success: 'bg-[var(--success)] text-white dark:text-black', warning: 'bg-[var(--warning)] text-black', info: 'bg-[var(--info)] text-white dark:text-black', error: 'bg-destructive text-white dark:text-black', muted: 'bg-muted text-muted-foreground', }, variant: { default: '', outlined: 'border-2 border-current', soft: 'bg-opacity-20', }, }, compoundVariants: [ { color: 'primary', variant: 'soft', class: 'bg-primary/20 text-primary' }, { color: 'secondary', variant: 'soft', class: 'bg-secondary/20 text-secondary-foreground' }, { color: 'destructive', variant: 'soft', class: 'bg-destructive/20 text-destructive' }, { color: 'success', variant: 'soft', class: 'bg-[var(--success)]/20 text-[var(--success)]' }, { color: 'warning', variant: 'soft', class: 'bg-[var(--warning)]/20 text-[var(--warning)]' }, { color: 'info', variant: 'soft', class: 'bg-[var(--info)]/20 text-[var(--info)]' }, { color: 'error', variant: 'soft', class: 'bg-destructive/20 text-destructive' }, ], defaultVariants: { size: 'default', rounded: 'default', color: 'default', }, }) export type AvatarVariants = VariantProps<typeof avatarVariants> export const avatarFallbackVariants = cva( 'flex size-full items-center justify-center rounded-full bg-muted font-medium', { variants: { size: { xs: 'text-[8px]', sm: 'text-xs', default: 'text-sm', lg: 'text-base', xl: 'text-lg', '2xl': 'text-xl', }, color: { default: '', primary: 'bg-primary text-primary-foreground', secondary: 'bg-secondary text-secondary-foreground', destructive: 'bg-destructive text-destructive-foreground', success: 'bg-[var(--success)] text-white dark:text-black', warning: 'bg-[var(--warning)] text-black', info: 'bg-[var(--info)] text-white dark:text-black', error: 'bg-destructive text-white dark:text-black', muted: 'bg-muted text-muted-foreground', }, }, defaultVariants: { size: 'default', color: 'default', }, }, ) export type AvatarFallbackVariants = VariantProps<typeof avatarFallbackVariants> -
app/components/ui/avatar/index.ts 0.5 kB
export { default as Avatar } from './Avatar.vue' export { default as AvatarFallback } from './AvatarFallback.vue' export { default as AvatarImage } from './AvatarImage.vue' export { default as AvatarGroup } from './AvatarGroup.vue' // Re-export variant API from the sibling file (kept separate to avoid the // Avatar.vue <-> index.ts circular import that broke dev SSR). export { avatarVariants, avatarFallbackVariants, type AvatarVariants, type AvatarFallbackVariants, } from './avatar.variants'
Raw manifest: https://uipkge.dev/r/vue/avatar.json