Avatar

The <smart-avatar> component renders a user/entity avatar in either single or grouped form. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract AvatarBaseComponent defines the shared API — imageUrl, initials, size, shape, notificationPosition, group (for stacked/multi-avatar display), optional IAvatarOptions, cssClass (alias class), and a derived isGroup computed signal that is true when group() has at least one item. AvatarStandardComponent is a barebones placeholder concrete implementation. AvatarComponent is the public wrapper that renders AvatarStandardComponent by default and accepts a custom replacement via AVATAR_STANDARD_COMPONENT_TOKEN.


Usage

<smart-avatar [initials]="initials" [size]="size" [shape]="shape" />

<smart-avatar [group]="team" [options]="groupOptions" size="sm" />

Components

AvatarComponent (<smart-avatar>)

Main wrapper component. Renders AvatarStandardComponent by default. When AVATAR_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet.

AvatarStandardComponent (<smart-avatar-standard>)

Barebones placeholder concrete implementation. Renders a host <span> carrying the external cssClass plus data-size and data-shape attributes. Inside:

  • Group mode (isGroup() is true): one <span class="smart-avatar-group-item"> per IAvatarItem (tracked by id). Each item renders an <img> when the item has imageUrl, otherwise a <span class="smart-avatar-initials"> with the item's initials.
  • Single mode (isGroup() is false): an <img> when imageUrl() is set, otherwise <span class="smart-avatar-initials"> when initials() is set, otherwise a <span aria-hidden="true" class="smart-avatar-placeholder">·</span> fallback.

It does not include Tailwind UI styling — it exists solely as the default structural placeholder until a custom implementation is registered through the token.

AvatarPresetComponent (<smart-avatar-preset>)

Styled variation that extends AvatarBaseComponent and is a drop-in replacement for AvatarStandardComponent. Register it via AVATAR_STANDARD_COMPONENT_TOKEN to restyle every <smart-avatar>, or use the <smart-avatar-preset> selector directly. It renders three content modes — an <img> (when imageUrl is set), an initials chip (when initials is set or options.placeholderType === 'initials'), or an SVG icon placeholder (the default) — across the full SmartAvatarSize scale (xs→size-8, sm→size-9.5, md→size-11, lg→size-15.5, xl→size-20) in both circle (rounded-full) and rounded (rounded-lg) shapes. When notificationPosition (top/bottom) is set the avatar is wrapped in a relative container with a corner status dot; when group() is non-empty it renders an overlapping stacked group (negative -space-x-2, ringed members), reversed when options.stackDirection === 'bottom-to-top'. All classes are smart:-prefixed Tailwind with explicit dark: variants. The class recipes live in preset/preset-classes.util.ts (getImageClasses, getInitialsClasses, getIconWrapperClasses, getStatusClasses, getGroupContainerClasses, getGroupItemImageClasses, getGroupItemInitialsClasses).

Because AvatarComponent renders injected components via NgComponentOutlet (which passes inputs by canonical name), AvatarPresetComponent overrides cssClass as input<string>('') without the class alias. Bind it as [cssClass] when using the <smart-avatar-preset> selector directly, or just pass class on <smart-avatar> (the wrapper forwards it).

AvatarBaseComponent (abstract)

Abstract base directive for extending custom avatar implementations. Static smartType: DynamicComponentType = 'avatar'. Exposes:

  • imageUrl: InputSignal<string | undefined>
  • initials: InputSignal<string | undefined>
  • size: InputSignal<SmartAvatarSize> (default 'md')
  • shape: InputSignal<SmartAvatarShape> (default 'circle')
  • notificationPosition: InputSignal<'top' | 'bottom' | undefined>
  • group: InputSignal<IAvatarItem[] | undefined>
  • options: InputSignal<IAvatarOptions | undefined>
  • cssClass: InputSignal<string> (alias class, default '')
  • isGroup = computed(() => !!this.group()?.length)

API

Inputs

InputTypeDefaultDescription
imageUrlInputSignal<string | undefined>-URL of the avatar image (single mode)
initialsInputSignal<string | undefined>-Initials shown when no imageUrl is provided (single mode)
sizeInputSignal<SmartAvatarSize>'md'Avatar size: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
shapeInputSignal<SmartAvatarShape>'circle'Avatar shape: 'circle' | 'rounded'
notificationPositionInputSignal<'top' | 'bottom' | undefined>-Reserved for custom implementations to position a notification dot/badge
groupInputSignal<IAvatarItem[] | undefined>-When set, renders a group of avatars instead of a single one
optionsInputSignal<IAvatarOptions | undefined>-Optional configuration (placeholder type, stack direction)
classInputSignal<string>''External CSS classes (alias for cssClass)

IAvatarItem

Each item in a group must have a unique id (used as the @for track key). When imageUrl is present the item renders as an <img>; otherwise as initials.

IAvatarOptions

  • placeholderType (default 'icon') — the fallback when there is no imageUrl. Both variants follow the same rule: initials are shown whenever they are set. Without initials, AvatarPresetComponent renders an SVG icon ('icon') or an empty initials chip ('initials'), while AvatarStandardComponent renders a · placeholder. The standard also exposes the value as data-placeholder-type on its root <span>.
  • stackDirection (default 'top-to-bottom') — purely visual, styled by the preset ('bottom-to-top' reverses the overlapping stack). The standard exposes it as data-stack-direction on the group container (the root <span>, only in group mode), so plain CSS can target it.

Single vs Group Mode

The component automatically detects mode from group():

  • Group mode: when group() is a non-empty array, isGroup() is true and the component iterates group(). The single-mode imageUrl/initials/placeholder branch is not rendered.
  • Single mode: when group() is undefined or [], isGroup() is false and the component renders imageUrl (or initials, or placeholder) as a single avatar.

AVATAR_STANDARD_COMPONENT_TOKEN

InjectionToken that allows replacing the default AvatarStandardComponent with a custom implementation. Provide a Type<AvatarBaseComponent> to override.

Extending the base class

import {
  ChangeDetectionStrategy,
  Component,
  computed,
  input,
  signal,
  ViewEncapsulation,
} from '@angular/core';

import {
  AvatarBaseComponent,
  AvatarComponent,
  AVATAR_STANDARD_COMPONENT_TOKEN,
  IAvatarItem,
  SmartAvatarSize,
} from '@smartsoft001/angular';

@Component({
  selector: 'docs-custom-avatar',
  template: `
    <span [class]="containerClasses()">
      @if (isGroup()) {
        @for (item of group(); track item.id) {
          <span class="docs-avatar__group-item">
            @if (item.imageUrl; as url) {
              <img [src]="url" alt="" />
            } @else {
              <span class="docs-avatar__initials">{{ item.initials }}</span>
            }
          </span>
        }
      } @else if (imageUrl(); as url) {
        <img [src]="url" alt="" />
      } @else if (initials(); as text) {
        <span class="docs-avatar__initials">{{ text }}</span>
      } @else {
        <span class="docs-avatar__placeholder" aria-hidden="true"
          >&middot;</span
        >
      }

      @if (notificationPosition(); as position) {
        <span
          class="docs-avatar__notification"
          [attr.data-position]="position"
        ></span>
      }
    </span>
  `,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomAvatarComponent extends AvatarBaseComponent {
  // NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
  override cssClass = input<string>('');

  containerClasses = computed(() =>
    [
      'docs-avatar',
      `docs-avatar--${this.size()}`,
      `docs-avatar--${this.shape()}`,
      this.cssClass(),
    ]
      .filter(Boolean)
      .join(' '),
  );
}

@Component({
  selector: 'docs-avatar-custom-example',
  imports: [AvatarComponent],
  providers: [
    {
      provide: AVATAR_STANDARD_COMPONENT_TOKEN,
      useValue: CustomAvatarComponent,
    },
  ],
  template: `
    <smart-avatar
      [initials]="initials"
      [size]="size()"
      shape="rounded"
      notificationPosition="top"
    />

    <smart-avatar [group]="team" size="sm" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvatarCustomExampleComponent {
  initials = 'TW';
  size = signal<SmartAvatarSize>('md');

  team: IAvatarItem[] = [
    { id: 'ana', initials: 'AK' },
    { id: 'bo', initials: 'BS' },
    { id: 'cai', initials: 'CL' },
  ];
}

Source

The component lives in packages/shared/angular/src/lib/components/avatar and is documented for Claude Code by the angular-components-avatar skill.