Avatar
Displays a user's image, with a fallback while it loads or when it fails.
- v1.0.0
- WAI-ARIA: `img` element's
- SSR
- Zoneless
- 1.4 kB gzip
import { Component } from '@angular/core';
import { Avatar, AvatarFallback, AvatarImage } from '@/ui/avatar/avatar';
/**
* A picture kept with the code, so the example loads nothing from the
* network. Use your users' image URLs.
*/
const PICTURE =
'data:image/svg+xml,' +
encodeURIComponent(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="#a5b4fc"/><circle cx="16" cy="13" r="6" fill="#4338ca"/><path d="M5 32a11 11 0 0 1 22 0z" fill="#4338ca"/></svg>',
);
/** An avatar with an image, and one that falls back to initials. */
@Component({
selector: 'example-avatar-demo',
imports: [Avatar, AvatarFallback, AvatarImage],
template: `
<div class="flex items-center gap-4">
<span ndAvatar>
<img ndAvatarImage [src]="picture" alt="Ada Lovelace" />
<span ndAvatarFallback>AL</span>
</span>
<span ndAvatar size="lg">
<img ndAvatarImage alt="Grace Hopper" />
<span ndAvatarFallback>GH</span>
</span>
<span ndAvatar size="sm">
<span ndAvatarFallback>KJ</span>
</span>
</div>
`,
})
export class AvatarDemoExample {
protected readonly picture = PICTURE;
}
An avatar shows a user's picture, with a fallback such as their initials
until the image loads, or if it fails, so there's never a broken image.
The image's alt names the avatar; once it has loaded, the fallback is
hidden from screen readers, so the avatar is announced once. Avatars come
in three sizes, and a group overlaps them with a count of the rest.
Installation
npx @ndop/cli add avatarpnpm dlx @ndop/cli add avataryarn dlx @ndop/cli add avatarbunx @ndop/cli add avatarInstall the dependencies
npm install @ndop/primitives @ndop/corepnpm add @ndop/primitives @ndop/coreyarn add @ndop/primitives @ndop/corebun add @ndop/primitives @ndop/coreCopy the code into your project
At the paths your
ndop.jsonaliases point to; these use the defaults.src/app/ui/avatar/avatar.tsimport { computed, Directive, input } from '@angular/core'; import { NdAvatar, NdAvatarFallback, NdAvatarImage, } from '@ndop/primitives/avatar'; import { cn } from '@/lib/utils'; /** Size of an {@link Avatar}. */ export type AvatarSize = 'default' | 'sm' | 'lg'; /** * A user's picture, with a fallback, such as initials, shown until the * image loads or when it fails (`@ndop/primitives/avatar`). * * @example * ```html * <span ndAvatar> * <img ndAvatarImage [src]="user.photo" [alt]="user.name" /> * <span ndAvatarFallback>VM</span> * </span> * ``` */ @Directive({ selector: '[ndAvatar]', hostDirectives: [NdAvatar], host: { 'data-slot': 'avatar', '[attr.data-size]': 'size()', '[class]': 'classes()', }, }) export class Avatar { /** Size. */ readonly size = input<AvatarSize>('default'); /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6', this.class(), ), ); } /** * An avatar's image. It covers the fallback once loaded; its `alt` names * the avatar. * * @example * ```html * <img ndAvatarImage src="/me.png" alt="Valery Melou" /> * ``` */ @Directive({ selector: 'img[ndAvatarImage]', hostDirectives: [{ directive: NdAvatarImage, inputs: ['src'] }], host: { 'data-slot': 'avatar-image', '[class]': 'classes()' }, }) export class AvatarImage { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'aspect-square size-full', 'absolute inset-0 data-[status=error]:hidden data-[status=loading]:opacity-0', this.class(), ), ); } /** * What an avatar shows until its image loads, or when it fails. * * @example * ```html * <span ndAvatarFallback>VM</span> * ``` */ @Directive({ selector: '[ndAvatarFallback]', hostDirectives: [NdAvatarFallback], host: { 'data-slot': 'avatar-fallback', '[class]': 'classes()' }, }) export class AvatarFallback { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( // Foreground text: shadcn's muted text on the muted fill is 4.3:1, // under WCAG's 4.5:1 for text this size. 'flex size-full items-center justify-center rounded-full bg-muted text-sm text-foreground group-data-[size=sm]/avatar:text-xs', 'data-[status=loaded]:invisible', this.class(), ), ); } /** * A status dot on an avatar's corner, such as "online". Say the status in * text too, for screen readers. * * @example * ```html * <span ndAvatarBadge><span class="sr-only">Online</span></span> * ``` */ @Directive({ selector: '[ndAvatarBadge]', host: { 'data-slot': 'avatar-badge', '[class]': 'classes()' }, }) export class AvatarBadge { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'absolute end-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none', 'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden', 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2', 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2', this.class(), ), ); } /** * Overlapping avatars, such as a team's. * * @example * ```html * <div ndAvatarGroup>…</div> * ``` */ @Directive({ selector: '[ndAvatarGroup]', host: { 'data-slot': 'avatar-group', '[class]': 'classes()' }, }) export class AvatarGroup { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background', 'rtl:space-x-reverse', this.class(), ), ); } /** * The count of avatars left out of a group, such as "+3". * * @example * ```html * <span ndAvatarGroupCount>+3</span> * ``` */ @Directive({ selector: '[ndAvatarGroupCount]', host: { 'data-slot': 'avatar-group-count', '[class]': 'classes()' }, }) export class AvatarGroupCount { /** Extra classes, merged last so they win. */ readonly class = input(''); protected readonly classes = computed(() => cn( 'relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3', this.class(), ), ); }src/app/lib/utils.ts/** * Class helpers used by every styled item. They come from `@ndop/core`; * this file is yours, so it is the place to customize them, for example by * wrapping `cn` with an extended tailwind-merge configuration for your own * utilities. * * @packageDocumentation */ export { cn, variants, type ClassValue, type VariantOf } from '@ndop/core';Update the import paths to match your project
The files import each other through the
@/uiand@/libaliases.
Usage
import { Avatar, AvatarFallback, AvatarImage } from '@/ui/avatar/avatar';<div class="flex items-center gap-4">
<span ndAvatar>
<img ndAvatarImage [src]="picture" alt="Ada Lovelace" />
<span ndAvatarFallback>AL</span>
</span>
<span ndAvatar size="lg">
<img ndAvatarImage alt="Grace Hopper" />
<span ndAvatarFallback>GH</span>
</span>
<span ndAvatar size="sm">
<span ndAvatarFallback>KJ</span>
</span>
</div>Examples
Avatar group
A team's avatars, with a count and a status badge.
import { Component } from '@angular/core';
import {
Avatar,
AvatarBadge,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
} from '@/ui/avatar/avatar';
/**
* A team's avatars overlapping, with a count of the others and a status
* badge that says its meaning in text too.
*/
@Component({
selector: 'example-avatar-group',
imports: [Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount],
template: `
<div class="flex flex-wrap items-center gap-8">
<div ndAvatarGroup role="group" aria-label="Design team">
@for (member of team; track member) {
<span ndAvatar>
<span ndAvatarFallback>{{ member }}</span>
</span>
}
<span ndAvatarGroupCount>+3</span>
</div>
<span ndAvatar size="lg">
<span ndAvatarFallback>VM</span>
<span ndAvatarBadge><span class="sr-only">Online</span></span>
</span>
</div>
`,
})
export class AvatarGroupExample {
protected readonly team = ['AL', 'GH', 'KJ'];
}
Accessibility
Implements the `img` element's pattern of the WAI-ARIA Authoring Practices.
Focus
- The avatar never receives focus and never changes focus.
Right-to-left
Nothing mirrors.
API reference
Avatar
[ndAvatar]A user's picture, with a fallback, such as initials, shown until the
image loads or when it fails (@ndop/primitives/avatar).
| Avatar binding | Type | Default |
|---|---|---|
[size]Size. | 'default' | 'sm' | 'lg' | 'default' |
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | avatar |
data-size | — |
data-status | — |
AvatarImage
img[ndAvatarImage]An avatar's image. It covers the fallback once loaded; its alt names
the avatar.
Manages aria-hidden.
| AvatarImage binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
[src]The image's URL; without one, the fallback shows. | string | null | undefined | — |
| Data attribute | Values |
|---|---|
data-slot | avatar-image |
data-status | — |
AvatarFallback
[ndAvatarFallback]What an avatar shows until its image loads, or when it fails.
Manages aria-hidden.
| AvatarFallback binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | avatar-fallback |
data-status | — |
AvatarBadge
[ndAvatarBadge]A status dot on an avatar's corner, such as "online". Say the status in text too, for screen readers.
| AvatarBadge binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | avatar-badge |
AvatarGroup
[ndAvatarGroup]Overlapping avatars, such as a team's.
| AvatarGroup binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | avatar-group |
AvatarGroupCount
[ndAvatarGroupCount]The count of avatars left out of a group, such as "+3".
| AvatarGroupCount binding | Type | Default |
|---|---|---|
[class]Extra classes, merged last so they win. | string | '' |
| Data attribute | Values |
|---|---|
data-slot | avatar-group-count |
Agent notes
Do
- Give the image an
altwith the person's name, oralt=""when their name is already next to it. - Use initials or an icon as the fallback.
- Say an avatar badge's meaning in text too, such as an
sr-only"Online".
Don't
- Don't use an avatar as the only way to tell people apart.
- Don't put interactive controls inside it; wrap it in a link or button.