Avatar
Avatar shows a person's profile image, with initials as the fallback. Use AvatarGroup to stack several people and AvatarStatus to show presence.
'use client';
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/components/nxds/components/avatar/avatar';
export default function AvatarDefault() {
return (
<Avatar size="md" shape="circle" ring={false}>
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace" />
<AvatarFallback>AL</AvatarFallback>
</Avatar>
);
}
Installation
npx nxds-cli@latest add avatar
Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.
Usage
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
AvatarStatus,
} from '@/components/nxds/components/avatar/avatar';
Composition
Avatar
├── AvatarImage
├── AvatarFallback
└── AvatarStatus
AvatarGroup
└── Avatar
├── AvatarImage
├── AvatarFallback
└── AvatarStatus
Examples
Status variants
Show online, away, busy and offline presence dots.
'use client';
import {
Avatar,
AvatarFallback,
AvatarStatus,
} from '@/components/nxds/components/avatar/avatar';
const STATUS_VALUES = ['online', 'away', 'busy', 'offline'] as const;
const PORTRAITS = [
{ name: 'Ada Lovelace', initials: 'AL', src: '/avatars/ada.svg' },
{ name: 'Grace Hopper', initials: 'GH', src: '/avatars/grace.svg' },
{ name: 'Katherine Johnson', initials: 'KJ', src: '/avatars/katherine.svg' },
{ name: 'Mary Jackson', initials: 'MJ', src: '/avatars/mary.svg' },
{ name: 'Dorothy Vaughan', initials: 'DV', src: '/avatars/dorothy.svg' },
];
export default function AvatarStatusVariants() {
return (
<div className="nx:flex nx:items-center nx:gap-4">
{STATUS_VALUES.map((status, index) => (
<Avatar key={status} size="lg">
<AvatarFallback>{PORTRAITS[index]?.initials ?? 'NA'}</AvatarFallback>
<AvatarStatus status={status} />
</Avatar>
))}
</div>
);
}
Group with max
Collapse extra avatars into a +N tile with max.
'use client';
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
AvatarStatus,
} from '@/components/nxds/components/avatar/avatar';
const STATUS_VALUES = ['online', 'away', 'busy', 'offline'] as const;
const PORTRAITS = [
{ name: 'Ada Lovelace', initials: 'AL', src: '/avatars/ada.svg' },
{ name: 'Grace Hopper', initials: 'GH', src: '/avatars/grace.svg' },
{ name: 'Katherine Johnson', initials: 'KJ', src: '/avatars/katherine.svg' },
{ name: 'Mary Jackson', initials: 'MJ', src: '/avatars/mary.svg' },
{ name: 'Dorothy Vaughan', initials: 'DV', src: '/avatars/dorothy.svg' },
];
function TeamAvatar({
person,
status,
}: {
person: { name: string; initials: string; src: string };
status?: (typeof STATUS_VALUES)[number];
}) {
return (
<Avatar>
<AvatarImage src={person.src} alt={person.name} />
<AvatarFallback>{person.initials}</AvatarFallback>
{status ? <AvatarStatus status={status} /> : null}
</Avatar>
);
}
export default function AvatarGroupWithMax() {
return (
<AvatarGroup max={3} role="group" aria-label="Project team">
{PORTRAITS.map((person, index) => (
<TeamAvatar
key={person.name}
person={person}
status={STATUS_VALUES[index % STATUS_VALUES.length]}
/>
))}
</AvatarGroup>
);
}
All sizes
Compare every size, both shapes and the fallback states.
All Sizes (Circle)
Shapes
Fallback States
Status, Ring, and Group
'use client';
import { UserIcon } from '@phosphor-icons/react/ssr';
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
AvatarStatus,
} from '@/components/nxds/components/avatar/avatar';
const AVATAR_SIZES = [
'2xs',
'xs',
'sm',
'md',
'lg',
'xl',
'2xl',
'3xl',
'4xl',
] as const;
const STATUS_VALUES = ['online', 'away', 'busy', 'offline'] as const;
const PORTRAITS = [
{ name: 'Ada Lovelace', initials: 'AL', src: '/avatars/ada.svg' },
{ name: 'Grace Hopper', initials: 'GH', src: '/avatars/grace.svg' },
{ name: 'Katherine Johnson', initials: 'KJ', src: '/avatars/katherine.svg' },
{ name: 'Mary Jackson', initials: 'MJ', src: '/avatars/mary.svg' },
{ name: 'Dorothy Vaughan', initials: 'DV', src: '/avatars/dorothy.svg' },
];
function avatarLabel(size: (typeof AVATAR_SIZES)[number]) {
if (size === '2xs') return '2X';
if (size === 'xs') return 'XS';
return size.toUpperCase();
}
function TeamAvatar({
person,
status,
}: {
person: { name: string; initials: string; src: string };
status?: (typeof STATUS_VALUES)[number];
}) {
return (
<Avatar>
<AvatarImage src={person.src} alt={person.name} />
<AvatarFallback>{person.initials}</AvatarFallback>
{status ? <AvatarStatus status={status} /> : null}
</Avatar>
);
}
export default function AvatarAllSizes() {
return (
<div className="nx:flex nx:flex-col nx:gap-8">
<div>
<h3 className="nx:mb-4 nx:typography-label-default nx:text-foreground">
All Sizes (Circle)
</h3>
<div className="nx:flex nx:items-end nx:gap-4">
{AVATAR_SIZES.map((size) => (
<div
key={size}
className="nx:flex nx:flex-col nx:items-center nx:gap-2"
>
<Avatar size={size}>
<AvatarImage
src="/avatars/ada.svg"
alt={`Ada Lovelace ${size}`}
/>
<AvatarFallback>{avatarLabel(size)}</AvatarFallback>
</Avatar>
<span className="nx:typography-label-small nx:text-muted-foreground">
{size}
</span>
</div>
))}
</div>
</div>
<div>
<h3 className="nx:mb-4 nx:typography-label-default nx:text-foreground">
Shapes
</h3>
<div className="nx:flex nx:items-center nx:gap-4">
<div className="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" shape="circle">
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace circle" />
<AvatarFallback>AL</AvatarFallback>
</Avatar>
<span className="nx:typography-label-small nx:text-muted-foreground">
circle
</span>
</div>
<div className="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" shape="rounded">
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace rounded" />
<AvatarFallback>AL</AvatarFallback>
</Avatar>
<span className="nx:typography-label-small nx:text-muted-foreground">
rounded
</span>
</div>
</div>
</div>
<div>
<h3 className="nx:mb-4 nx:typography-label-default nx:text-foreground">
Fallback States
</h3>
<div className="nx:flex nx:items-center nx:gap-4">
<div className="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" role="img" aria-label="Ada Lovelace">
<AvatarFallback aria-hidden="true">AL</AvatarFallback>
</Avatar>
<span className="nx:typography-label-small nx:text-muted-foreground">
Initials
</span>
</div>
<div className="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" role="img" aria-label="Unknown user">
<AvatarFallback aria-hidden="true">
<UserIcon aria-hidden="true" className="nx:size-6" />
</AvatarFallback>
</Avatar>
<span className="nx:typography-label-small nx:text-muted-foreground">
Icon
</span>
</div>
</div>
</div>
<div>
<h3 className="nx:mb-4 nx:typography-label-default nx:text-foreground">
Status, Ring, and Group
</h3>
<div className="nx:flex nx:flex-wrap nx:items-center nx:gap-6">
<Avatar size="xl" ring>
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace selected" />
<AvatarFallback>AL</AvatarFallback>
<AvatarStatus status="online" />
</Avatar>
<AvatarGroup max={3}>
{PORTRAITS.map((person, index) => (
<TeamAvatar
key={person.name}
person={person}
status={STATUS_VALUES[index % STATUS_VALUES.length]}
/>
))}
</AvatarGroup>
</div>
</div>
</div>
);
}
This example also needs:
npm install @phosphor-icons/react@^2.1.10
Props
Avatar
| Prop | Type | Default | Description |
|---|---|---|---|
ring | boolean | false | Draws a non-interactive emphasis ring, such as for a selected person. Put focus rings on the interactive wrapper. |
shape | "rounded" | "circle" | null | "circle" | —No description |
size | "sm" | "lg" | "2xs" | "xs" | "md" | "xl" | "2xl" | "3xl" | "4xl" | null | "md" | —No description |
AvatarFallback
AvatarFallback adds no props of its own; it accepts the props of the element or primitive it wraps.
AvatarGroup
| Prop | Type | Default | Description |
|---|---|---|---|
max | number | —No default documented | Number of avatars shown before the rest collapse into a +N tile. |
shape | "rounded" | "circle" | null | "circle" | —No description |
size | "sm" | "lg" | "2xs" | "xs" | "md" | "xl" | "2xl" | "3xl" | "4xl" | null | "md" | —No description |
AvatarImage
AvatarImage adds no props of its own; it accepts the props of the element or primitive it wraps.
AvatarStatus
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | —No default documented | Screen reader text for the dot, the capitalized |
status | "online" | "away" | "busy" | "offline" | null | "online" | —No description |
Avatar shows a person's profile image, with initials as the fallback. Use AvatarGroup to stack several people and AvatarStatus to show presence.
<script setup lang="ts">
import { Avatar, AvatarFallback, AvatarImage } from '@/components/avatar';
</script>
<template>
<Avatar>
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace" />
<AvatarFallback>AL</AvatarFallback>
</Avatar>
</template>
Installation
- 1
Install the following dependencies:
npm install @vueuse/core@^14.1.0 class-variance-authority@^0.7.1 reka-ui@^2.11.0 - 2
Copy and paste the following code into your project.
components/avatar/Avatar.vue <script setup lang="ts"> import { reactiveOmit } from '@vueuse/core'; import { AvatarRoot, type AvatarRootProps } from 'reka-ui'; import { computed, type HTMLAttributes, inject } from 'vue'; import { cn } from '../../lib/utils'; import { AvatarGroupKey, type AvatarVariants, avatarVariants } from '.'; export interface AvatarProps extends AvatarRootProps { size?: AvatarVariants['size']; shape?: AvatarVariants['shape']; /** * Draws a non-interactive emphasis ring, such as for a selected person. Put focus rings on the interactive wrapper. */ ring?: boolean; class?: HTMLAttributes['class']; } const props = withDefaults(defineProps<AvatarProps>(), { ring: false }); const delegated = reactiveOmit(props, 'class', 'size', 'shape', 'ring'); const group = inject(AvatarGroupKey, undefined); const size = computed(() => props.size ?? group?.size); const shape = computed(() => props.shape ?? group?.shape); </script> <template> <AvatarRoot v-bind="delegated" data-slot="avatar" :data-size="size ?? 'md'" :data-shape="shape ?? 'circle'" :data-ring="ring || undefined" :class=" cn( avatarVariants({ size, shape }), ring && 'nx:ring-2 nx:ring-primary-border nx:ring-offset-2 nx:ring-offset-surface', props.class ) " > <slot /> </AvatarRoot> </template>components/avatar/AvatarFallback.vue <script setup lang="ts"> import { reactiveOmit } from '@vueuse/core'; import { AvatarFallback, type AvatarFallbackProps as RekaAvatarFallbackProps, } from 'reka-ui'; import type { HTMLAttributes } from 'vue'; import { cn } from '../../lib/utils'; export interface AvatarFallbackProps extends RekaAvatarFallbackProps { class?: HTMLAttributes['class']; } const props = defineProps<AvatarFallbackProps>(); const delegated = reactiveOmit(props, 'class'); </script> <template> <AvatarFallback v-bind="delegated" data-slot="avatar-fallback" :class=" cn( 'nx:flex nx:size-full nx:items-center nx:justify-center nx:rounded-[inherit] nx:bg-muted nx:text-foreground nx:font-medium nx:leading-none', props.class ) " > <slot /> </AvatarFallback> </template>components/avatar/AvatarGroup.vue <script setup lang="ts"> import { type HTMLAttributes, provide, type VNode } from 'vue'; import { flattenedNodes } from '../../lib/slots'; import { cn } from '../../lib/utils'; import Avatar from './Avatar.vue'; import AvatarFallback from './AvatarFallback.vue'; import { AvatarGroupKey, type AvatarVariants } from '.'; export interface AvatarGroupProps { size?: AvatarVariants['size']; shape?: AvatarVariants['shape']; /** * Number of avatars shown before the rest collapse into a +N tile. */ max?: number; class?: HTMLAttributes['class']; } const props = defineProps<AvatarGroupProps>(); const slots = defineSlots<{ default?: () => VNode[] }>(); provide(AvatarGroupKey, props); function splitMembers() { const members = flattenedNodes(slots.default?.()); const hasMax = typeof props.max === 'number' && props.max > 0; const visible = hasMax ? members.slice(0, props.max) : members; return { visible, overflow: members.length - visible.length }; } </script> <template> <div data-slot="avatar-group" :data-size="size" :data-shape="shape" :class=" cn( 'nx:flex nx:items-center', // Overlap scales with each avatar's font-size (set per size), so the // tuck stays ~proportional from 2xs to 4xl instead of a fixed px. 'nx:[&>[data-slot=avatar]+[data-slot=avatar]]:ms-[-0.7em]', 'nx:*:data-[slot=avatar]:relative', 'nx:*:data-[slot=avatar]:ring-[0.1em] nx:*:data-[slot=avatar]:ring-surface', props.class ) " > <!-- A one-item v-for runs the default slot once per render. --> <template v-for="({ visible, overflow }, split) in [splitMembers()]" :key="split" > <component :is="member" v-for="(member, index) in visible" :key="member.key ?? index" /> <Avatar v-if="overflow > 0" role="img" :aria-label="`${overflow} more`"> <AvatarFallback aria-hidden="true">+{{ overflow }}</AvatarFallback> </Avatar> </template> </div> </template>components/avatar/AvatarImage.vue <script setup lang="ts"> import { reactiveOmit } from '@vueuse/core'; import { AvatarImage, type AvatarImageEmits, type AvatarImageProps as RekaAvatarImageProps, useForwardPropsEmits, } from 'reka-ui'; import type { HTMLAttributes } from 'vue'; import { cn } from '../../lib/utils'; export interface AvatarImageProps extends RekaAvatarImageProps { class?: HTMLAttributes['class']; } const props = defineProps<AvatarImageProps>(); const emits = defineEmits<AvatarImageEmits>(); const delegated = reactiveOmit(props, 'class'); const forwarded = useForwardPropsEmits(delegated, emits); </script> <template> <AvatarImage v-bind="forwarded" data-slot="avatar-image" decoding="async" :class=" cn( 'nx:aspect-square nx:size-full nx:rounded-[inherit] nx:object-cover', 'nx:animate-in nx:fade-in-0 nx:duration-fast nx:ease-enter', props.class ) " /> </template>components/avatar/AvatarStatus.vue <script setup lang="ts"> import { computed, type HTMLAttributes } from 'vue'; import { cn } from '../../lib/utils'; import { type AvatarStatusVariants, avatarStatusVariants, STATUS_LABELS, } from '.'; export interface AvatarStatusProps { status?: AvatarStatusVariants['status']; /** * Screen reader text for the dot, the capitalized `status` by default. Pass `''` when visible text already shows presence. */ label?: string; class?: HTMLAttributes['class']; } const props = withDefaults(defineProps<AvatarStatusProps>(), { status: 'online', }); const resolvedLabel = computed( () => props.label ?? STATUS_LABELS[props.status ?? 'online'] ); </script> <template> <span data-slot="avatar-status" :data-status="status" :class="cn(avatarStatusVariants({ status }), props.class)" > <span class="nx:sr-only">{{ resolvedLabel }}</span> </span> </template>components/avatar/index.ts import { cva, type VariantProps } from 'class-variance-authority'; import type { InjectionKey } from 'vue'; import type { AvatarGroupProps } from './AvatarGroup.vue'; export { default as Avatar, type AvatarProps } from './Avatar.vue'; export { default as AvatarFallback, type AvatarFallbackProps, } from './AvatarFallback.vue'; export { default as AvatarGroup, type AvatarGroupProps, } from './AvatarGroup.vue'; export { default as AvatarImage, type AvatarImageProps, } from './AvatarImage.vue'; export { default as AvatarStatus, type AvatarStatusProps, } from './AvatarStatus.vue'; // Avatar falls back to this for `size`/`shape` when its own prop is unset. export const AvatarGroupKey: InjectionKey< Pick<AvatarGroupProps, 'size' | 'shape'> > = Symbol('avatar-group'); export const avatarVariants = cva( 'nx:relative nx:flex nx:shrink-0 nx:after:pointer-events-none nx:after:absolute nx:after:inset-0 nx:after:rounded-[inherit] nx:after:outline nx:after:outline-1 nx:after:-outline-offset-1 nx:after:outline-border-hairline', { variants: { size: { '2xs': 'nx:size-5 nx:text-[length:calc(var(--nx-typography-size-base)*0.625)]', xs: 'nx:size-6 nx:text-[length:calc(var(--nx-typography-size-base)*0.6875)]', sm: 'nx:size-8 nx:text-[length:calc(var(--nx-typography-size-base)*0.75)]', md: 'nx:size-10 nx:text-[length:calc(var(--nx-typography-size-base)*1)]', lg: 'nx:size-12 nx:text-[length:calc(var(--nx-typography-size-base)*1.125)]', xl: 'nx:size-14 nx:text-[length:calc(var(--nx-typography-size-base)*1.25)]', '2xl': 'nx:size-16 nx:text-[length:calc(var(--nx-typography-size-base)*1.5)]', '3xl': 'nx:size-20 nx:text-[length:calc(var(--nx-typography-size-base)*1.875)]', '4xl': 'nx:size-24 nx:text-[length:calc(var(--nx-typography-size-base)*2.25)]', }, shape: { circle: 'nx:rounded-full', rounded: 'nx:rounded-lg', }, }, defaultVariants: { size: 'md', shape: 'circle', }, } ); export const avatarStatusVariants = cva( 'nx:absolute nx:right-[0.04em] nx:bottom-[0.04em] nx:size-[0.6em] nx:rounded-full nx:ring-[0.1em] nx:ring-surface', { variants: { status: { online: 'nx:bg-success-background', away: 'nx:bg-warning-background', busy: 'nx:bg-error-background', offline: 'nx:bg-muted', }, }, defaultVariants: { status: 'online', }, } ); export const STATUS_LABELS = { online: 'Online', away: 'Away', busy: 'Busy', offline: 'Offline', } as const satisfies Record< NonNullable<VariantProps<typeof avatarStatusVariants>['status']>, string >; export type AvatarVariants = VariantProps<typeof avatarVariants>; export type AvatarStatusVariants = VariantProps<typeof avatarStatusVariants>;lib/slots.ts import { Comment, Fragment, Text, type VNode } from 'vue'; /** The nodes a slot renders, without comments and whitespace-only text. */ export function renderedNodes(nodes: VNode[] | undefined) { return (nodes ?? []).filter( (node) => node.type !== Comment && !(node.type === Text && !String(node.children).trim()) ); } /** {@link renderedNodes} with each `v-for` or `<template>` fragment unwrapped into its children. */ export function flattenedNodes(nodes: VNode[] | undefined): VNode[] { return renderedNodes(nodes).flatMap((node) => node.type === Fragment && Array.isArray(node.children) ? flattenedNodes(node.children as VNode[]) : [node] ); } - 3
Keep each file at the path shown above it.
Paths are relative to
packages/vue/src/. Keep the same folders under your@/root, and the imports between files work without edits.
Usage
import { Avatar, AvatarFallback, AvatarGroup, AvatarImage, AvatarStatus } from '@/components/avatar';
Examples
Status variants
Show online, away, busy and offline presence dots.
<script setup lang="ts">
import { Avatar, AvatarFallback, AvatarStatus } from '@/components/avatar';
const people = [
{ initials: 'AL', status: 'online' },
{ initials: 'GH', status: 'away' },
{ initials: 'KJ', status: 'busy' },
{ initials: 'MJ', status: 'offline' },
] as const;
</script>
<template>
<div class="nx:flex nx:items-center nx:gap-4">
<Avatar v-for="person in people" :key="person.status" size="lg">
<AvatarFallback>{{ person.initials }}</AvatarFallback>
<AvatarStatus :status="person.status" />
</Avatar>
</div>
</template>
Group with max
Collapse extra avatars into a +N tile with max.
<script setup lang="ts">
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
AvatarStatus,
} from '@/components/avatar';
const people = [
{
name: 'Ada Lovelace',
initials: 'AL',
src: '/avatars/ada.svg',
status: 'online',
},
{
name: 'Grace Hopper',
initials: 'GH',
src: '/avatars/grace.svg',
status: 'away',
},
{
name: 'Katherine Johnson',
initials: 'KJ',
src: '/avatars/katherine.svg',
status: 'busy',
},
{
name: 'Mary Jackson',
initials: 'MJ',
src: '/avatars/mary.svg',
status: 'offline',
},
{
name: 'Dorothy Vaughan',
initials: 'DV',
src: '/avatars/dorothy.svg',
status: 'online',
},
] as const;
</script>
<template>
<AvatarGroup :max="3" role="group" aria-label="Project team">
<Avatar v-for="person in people" :key="person.name">
<AvatarImage :src="person.src" :alt="person.name" />
<AvatarFallback>{{ person.initials }}</AvatarFallback>
<AvatarStatus :status="person.status" />
</Avatar>
</AvatarGroup>
</template>
All sizes
Compare every size, both shapes and the fallback states.
<script setup lang="ts">
import { PhUser } from '@phosphor-icons/vue';
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarImage,
AvatarStatus,
} from '@/components/avatar';
const sizes = [
'2xs',
'xs',
'sm',
'md',
'lg',
'xl',
'2xl',
'3xl',
'4xl',
] as const;
const people = [
{
name: 'Ada Lovelace',
initials: 'AL',
src: '/avatars/ada.svg',
status: 'online',
},
{
name: 'Grace Hopper',
initials: 'GH',
src: '/avatars/grace.svg',
status: 'away',
},
{
name: 'Katherine Johnson',
initials: 'KJ',
src: '/avatars/katherine.svg',
status: 'busy',
},
{
name: 'Mary Jackson',
initials: 'MJ',
src: '/avatars/mary.svg',
status: 'offline',
},
{
name: 'Dorothy Vaughan',
initials: 'DV',
src: '/avatars/dorothy.svg',
status: 'online',
},
] as const;
</script>
<template>
<div class="nx:flex nx:flex-col nx:gap-8">
<div>
<h3 class="nx:mb-4 nx:typography-label-default nx:text-foreground">
All Sizes (Circle)
</h3>
<div class="nx:flex nx:items-end nx:gap-4">
<div
v-for="size in sizes"
:key="size"
class="nx:flex nx:flex-col nx:items-center nx:gap-2"
>
<Avatar :size="size">
<AvatarImage src="/avatars/ada.svg" :alt="'Ada Lovelace ' + size" />
<AvatarFallback>{{
size === '2xs' ? '2X' : size.toUpperCase()
}}</AvatarFallback>
</Avatar>
<span class="nx:typography-label-small nx:text-muted-foreground">{{
size
}}</span>
</div>
</div>
</div>
<div>
<h3 class="nx:mb-4 nx:typography-label-default nx:text-foreground">
Shapes
</h3>
<div class="nx:flex nx:items-center nx:gap-4">
<div class="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" shape="circle">
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace circle" />
<AvatarFallback>AL</AvatarFallback>
</Avatar>
<span class="nx:typography-label-small nx:text-muted-foreground"
>circle</span
>
</div>
<div class="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" shape="rounded">
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace rounded" />
<AvatarFallback>AL</AvatarFallback>
</Avatar>
<span class="nx:typography-label-small nx:text-muted-foreground"
>rounded</span
>
</div>
</div>
</div>
<div>
<h3 class="nx:mb-4 nx:typography-label-default nx:text-foreground">
Fallback States
</h3>
<div class="nx:flex nx:items-center nx:gap-4">
<div class="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" role="img" aria-label="Ada Lovelace">
<AvatarFallback aria-hidden="true">AL</AvatarFallback>
</Avatar>
<span class="nx:typography-label-small nx:text-muted-foreground"
>Initials</span
>
</div>
<div class="nx:flex nx:flex-col nx:items-center nx:gap-2">
<Avatar size="xl" role="img" aria-label="Unknown user">
<AvatarFallback aria-hidden="true">
<PhUser aria-hidden="true" class="nx:size-6" />
</AvatarFallback>
</Avatar>
<span class="nx:typography-label-small nx:text-muted-foreground"
>Icon</span
>
</div>
</div>
</div>
<div>
<h3 class="nx:mb-4 nx:typography-label-default nx:text-foreground">
Status, Ring, and Group
</h3>
<div class="nx:flex nx:flex-wrap nx:items-center nx:gap-6">
<Avatar size="xl" ring>
<AvatarImage src="/avatars/ada.svg" alt="Ada Lovelace selected" />
<AvatarFallback>AL</AvatarFallback>
<AvatarStatus status="online" />
</Avatar>
<AvatarGroup :max="3">
<Avatar v-for="person in people" :key="person.name">
<AvatarImage :src="person.src" :alt="person.name" />
<AvatarFallback>{{ person.initials }}</AvatarFallback>
<AvatarStatus :status="person.status" />
</Avatar>
</AvatarGroup>
</div>
</div>
</div>
</template>
This example also needs:
npm install @phosphor-icons/vue@^2.2.1
Props
Avatar
| Prop | Type | Default | Description |
|---|---|---|---|
ring | boolean | false | Draws a non-interactive emphasis ring, such as for a selected person. Put focus rings on the interactive wrapper. |
shape | "circle" | "rounded" | null | "circle" | —No description |
size | "sm" | "lg" | "2xs" | "xs" | "md" | "xl" | "2xl" | "3xl" | "4xl" | null | "md" | —No description |
AvatarFallback
AvatarFallback adds no props of its own; it accepts the props of the element or primitive it wraps.
AvatarGroup
| Prop | Type | Default | Description |
|---|---|---|---|
max | number | —No default documented | Number of avatars shown before the rest collapse into a +N tile. |
shape | "circle" | "rounded" | null | "circle" | —No description |
size | "sm" | "lg" | "2xs" | "xs" | "md" | "xl" | "2xl" | "3xl" | "4xl" | null | "md" | —No description |
AvatarImage
AvatarImage adds no props of its own; it accepts the props of the element or primitive it wraps.
AvatarStatus
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | —No default documented | Screen reader text for the dot, the capitalized |
status | "online" | "away" | "busy" | "offline" | null | "online" | —No description |