- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Autocomplete
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Code Block
- Collapsible
- Combobox
- Command
- Context Menu
- Data Table
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input Group
- Input OTP
- Input
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Questionnaire
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner (Toast)
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Toggle Group
- Tooltip
- Trace
Avatar
An image element with a fallback for representing the user.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-preview',
imports: [HlmAvatarImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-row flex-wrap items-center gap-6 md:gap-12">
<hlm-avatar>
<img
hlmAvatarImage
src="/assets/avatar.png"
alt="spartan logo. Resembling a spartanic shield"
class="grayscale"
/>
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img
hlmAvatarImage
src="/assets/avatar.png"
alt="spartan logo. Resembling a spartanic shield"
class="grayscale"
/>
<span hlmAvatarFallback>RG</span>
<hlm-avatar-badge class="bg-red-600 dark:bg-red-800" />
</hlm-avatar>
<hlm-avatar-group class="grayscale">
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="spartan logo. Resembling a spartanic shield" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="spartan logo. Resembling a spartanic shield" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="spartan logo. Resembling a spartanic shield" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar-group-count>+3</hlm-avatar-group-count>
</hlm-avatar-group>
</div>
`,
})
export class AvatarPreview {}Installation
ng g @spartan-ng/cli:ui avatarnx g @spartan-ng/cli:ui avatarimport { DestroyRef, ElementRef, HostAttributeToken, Injector, NgZone, PLATFORM_ID, effect, inject, makeEnvironmentProviders, runInInjectionContext, type EnvironmentProviders } from '@angular/core';
import { OVERLAY_DEFAULT_CONFIG } from '@angular/cdk/overlay';
import { clsx, type ClassValue } from 'clsx';
import { isPlatformBrowser } from '@angular/common';
import { provideSpartanHlm } from '@spartan-ng/helm/utils';
import { twMerge } from 'tailwind-merge';
export function hlm(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Global map to track class managers per element
const elementClassManagers = new WeakMap<HTMLElement, ElementClassManager>();
// Global mutation observer for all elements
let globalObserver: MutationObserver | null = null;
const observedElements = new Set<HTMLElement>();
interface ElementClassManager {
element: HTMLElement;
sources: Map<number, { classes: Set<string>; order: number }>;
baseClasses: Set<string>;
isUpdating: boolean;
nextOrder: number;
hasInitialized: boolean;
restoreRafId: number | null;
/** Transitions are suppressed until the first effect writes correct classes */
transitionsSuppressed: boolean;
/** Original inline transition value to restore after suppression (empty string = none was set) */
previousTransition: string;
/** Original inline transition priority to preserve !important when restoring */
previousTransitionPriority: string;
}
let sourceCounter = 0;
/**
* This function dynamically adds and removes classes for a given element without requiring
* the a class binding (e.g. `[class]="..."`) which may interfere with other class bindings.
*
* 1. This will merge the existing classes on the element with the new classes.
* 2. It will also remove any classes that were previously added by this function but are no longer present in the new classes.
* 3. Multiple calls to this function on the same element will be merged efficiently.
*/
export function classes(computed: () => ClassValue[] | string, options: ClassesOptions = {}) {
runInInjectionContext(options.injector ?? inject(Injector), () => {
const elementRef = options.elementRef ?? inject(ElementRef);
const platformId = inject(PLATFORM_ID);
const destroyRef = inject(DestroyRef);
const baseClasses = inject(new HostAttributeToken('class'), { optional: true });
const element = elementRef.nativeElement;
// Create unique identifier for this source
const sourceId = sourceCounter++;
// Get or create the class manager for this element
let manager = elementClassManagers.get(element);
if (!manager) {
// Initialize base classes from variation (host attribute 'class')
const initialBaseClasses = new Set<string>();
if (baseClasses) {
toClassList(baseClasses).forEach((cls) => initialBaseClasses.add(cls));
}
manager = {
element,
sources: new Map(),
baseClasses: initialBaseClasses,
isUpdating: false,
nextOrder: 0,
hasInitialized: false,
restoreRafId: null,
transitionsSuppressed: false,
previousTransition: '',
previousTransitionPriority: '',
};
elementClassManagers.set(element, manager);
// Setup global observer if needed and register this element. Created outside the zone so
// class mutations don't trigger app-wide change detection (and loop if CD writes a class).
inject(NgZone).runOutsideAngular(() => setupGlobalObserver(platformId));
observedElements.add(element);
// Suppress transitions until the first effect writes correct classes and
// the browser has painted them. This prevents CSS transition animations
// during hydration when classes change from SSR state to client state.
if (isPlatformBrowser(platformId)) {
manager.previousTransition = element.style.getPropertyValue('transition');
manager.previousTransitionPriority = element.style.getPropertyPriority('transition');
element.style.setProperty('transition', 'none', 'important');
manager.transitionsSuppressed = true;
}
}
// Assign order once at registration time
const sourceOrder = manager.nextOrder++;
function updateClasses(): void {
// Get the new classes from the computed function
const newClasses = toClassList(computed());
// Update this source's classes, keeping the original order
manager!.sources.set(sourceId, {
classes: new Set(newClasses),
order: sourceOrder,
});
// Update the element
updateElement(manager!);
// Re-enable transitions after the first effect writes correct classes.
// Deferred to next animation frame so the browser paints the class change
// with transitions disabled first, then re-enables them.
if (manager!.transitionsSuppressed) {
manager!.transitionsSuppressed = false;
manager!.restoreRafId = requestAnimationFrame(() => {
manager!.restoreRafId = null;
restoreTransitionSuppression(manager!);
});
}
}
// Register cleanup with DestroyRef
destroyRef.onDestroy(() => {
if (manager!.restoreRafId !== null) {
cancelAnimationFrame(manager!.restoreRafId);
manager!.restoreRafId = null;
}
if (manager!.transitionsSuppressed) {
manager!.transitionsSuppressed = false;
restoreTransitionSuppression(manager!);
}
// Remove this source from the manager
manager!.sources.delete(sourceId);
// If no more sources, clean up the manager
if (manager!.sources.size === 0) {
cleanupManager(element);
} else {
// Update element without this source's classes
updateElement(manager!);
}
});
/**
* We need this effect to track changes to the computed classes. Ideally, we would use
* afterRenderEffect here, but that doesn't run in SSR contexts, so we use a standard
* effect which works in both browser and SSR.
*/
effect(updateClasses);
});
}
function restoreTransitionSuppression(manager: ElementClassManager): void {
const prev = manager.previousTransition;
if (prev) {
manager.element.style.setProperty('transition', prev, manager.previousTransitionPriority || undefined);
} else {
manager.element.style.removeProperty('transition');
}
}
// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types
function setupGlobalObserver(platformId: Object): void {
if (isPlatformBrowser(platformId) && !globalObserver) {
// Create single global observer that watches the entire document
globalObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const element = mutation.target as HTMLElement;
const manager = elementClassManagers.get(element);
// Only process elements we're managing
if (manager && observedElements.has(element)) {
if (manager.isUpdating) continue; // Ignore changes we're making
// Update base classes to include any externally added classes
const currentClasses = toClassList(element.className);
const allSourceClasses = new Set<string>();
// Collect all classes from all sources
for (const source of manager.sources.values()) {
for (const className of source.classes) {
allSourceClasses.add(className);
}
}
// Any classes not from sources become new base classes
manager.baseClasses.clear();
for (const className of currentClasses) {
if (!allSourceClasses.has(className)) {
manager.baseClasses.add(className);
}
}
updateElement(manager);
}
}
}
});
// Start observing the entire document for class attribute changes
globalObserver.observe(document, {
attributes: true,
attributeFilter: ['class'],
subtree: true, // Watch all descendants
});
}
}
function updateElement(manager: ElementClassManager): void {
if (manager.isUpdating) return; // Prevent recursive updates
manager.isUpdating = true;
// Handle initialization: capture base classes after first source registration
if (!manager.hasInitialized && manager.sources.size > 0) {
// Get current classes on element (may include SSR classes)
const currentClasses = toClassList(manager.element.className);
// Get all classes that will be applied by sources
const allSourceClasses = new Set<string>();
for (const source of manager.sources.values()) {
source.classes.forEach((className) => allSourceClasses.add(className));
}
// Only consider classes as "base" if they're not produced by any source
// This prevents SSR-rendered classes from being preserved as base classes
currentClasses.forEach((className) => {
if (!allSourceClasses.has(className)) {
manager.baseClasses.add(className);
}
});
manager.hasInitialized = true;
}
// Get classes from all sources, sorted by registration order (later takes precedence)
const sortedSources = Array.from(manager.sources.entries()).sort(([, a], [, b]) => a.order - b.order);
const allSourceClasses: string[] = [];
for (const [, source] of sortedSources) {
allSourceClasses.push(...source.classes);
}
// Combine base classes with all source classes, ensuring base classes take precedence
const classesToApply =
allSourceClasses.length > 0 || manager.baseClasses.size > 0
? hlm([...allSourceClasses, ...manager.baseClasses])
: '';
// Apply the classes to the element
if (manager.element.className !== classesToApply) {
manager.element.className = classesToApply;
}
manager.isUpdating = false;
}
function cleanupManager(element: HTMLElement): void {
// Remove from global tracking
observedElements.delete(element);
elementClassManagers.delete(element);
// If no more elements being tracked, cleanup global observer
if (observedElements.size === 0 && globalObserver) {
globalObserver.disconnect();
globalObserver = null;
}
}
interface ClassesOptions {
elementRef?: ElementRef<HTMLElement>;
injector?: Injector;
}
// Cache for parsed class lists to avoid repeated string operations
const classListCache = new Map<string, string[]>();
function toClassList(className: string | ClassValue[]): string[] {
// For simple string inputs, use cache to avoid repeated parsing
if (typeof className === 'string' && classListCache.has(className)) {
return classListCache.get(className)!;
}
const result = clsx(className)
.split(' ')
.filter((c) => c.length > 0);
// Cache string results, but limit cache size to prevent memory growth
if (typeof className === 'string' && classListCache.size < 1000) {
classListCache.set(className, result);
}
return result;
}
/**
* Provides default configuration for Spartan Helm components.
*
* This utility configures the Angular CDK overlay to disable the `usePopover`
* behavior introduced in Angular 21, which causes CDK overlay-based components
* (sheets, dialogs, tooltips, etc.) to render above `position: fixed` elements
* like `<hlm-toaster>`.
*
* @returns {EnvironmentProviders} Environment providers to be added to the application config.
*
* @example
* ```ts
* // app.config.ts
*
*
* export const appConfig: ApplicationConfig = {
* providers: [
* provideSpartanHlm(),
* // ... other providers
* ],
* };
* ```
*/
export function provideSpartanHlm(): EnvironmentProviders {
return makeEnvironmentProviders([
{
provide: OVERLAY_DEFAULT_CONFIG,
useValue: { usePopover: false },
},
]);
}import { BrnAvatar, BrnAvatarFallback, BrnAvatarImage } from '@spartan-ng/brain/avatar';
import { Directive, inject, input } from '@angular/core';
import { classes } from '@spartan-ng/helm/utils';
@Directive({
selector: '[hlmAvatarBadge],hlm-avatar-badge',
exportAs: 'hlmAvatarBadge',
host: {
'data-slot': 'avatar-badge',
},
})
export class HlmAvatarBadge {
constructor() {
classes(() => [
'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>ng-icon]:hidden',
'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
]);
}
}
@Directive({
selector: '[hlmAvatarFallback]',
exportAs: 'hlmAvatarFallback',
hostDirectives: [BrnAvatarFallback],
host: {
'data-slot': 'avatar-fallback',
},
})
export class HlmAvatarFallback {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroupCount],hlm-avatar-group-count',
exportAs: 'hlmAvatarGroupCount',
host: {
'data-slot': 'avatar-group-count',
},
})
export class HlmAvatarGroupCount {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>ng-icon]:text-base group-has-data-[size=lg]/avatar-group:[&>ng-icon]:text-xl group-has-data-[size=sm]/avatar-group:[&>ng-icon]:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroup],hlm-avatar-group',
exportAs: 'hlmAvatarGroup',
host: {
'data-slot': 'avatar-group',
},
})
export class HlmAvatarGroup {
constructor() {
classes(
() => '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
);
}
}
@Directive({
selector: 'img[hlmAvatarImage]',
exportAs: 'hlmAvatarImage',
hostDirectives: [{ directive: BrnAvatarImage, inputs: ['hostStyles'] }],
host: {
'data-slot': 'avatar-image',
},
})
export class HlmAvatarImage {
public readonly canShow = inject(BrnAvatarImage).canShow;
constructor() {
classes(() => 'rounded-full aspect-square size-full object-cover');
}
}
@Directive({
selector: '[hlmAvatar],hlm-avatar',
exportAs: 'hlmAvatar',
hostDirectives: [BrnAvatar],
host: {
'data-slot': 'avatar',
'[attr.data-size]': 'size()',
},
})
export class HlmAvatar {
public readonly size = input<'default' | 'sm' | 'lg'>('default');
constructor() {
classes(
() =>
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar after:border-border relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
);
}
}
export const HlmAvatarImports = [
HlmAvatar,
HlmAvatarBadge,
HlmAvatarFallback,
HlmAvatarGroup,
HlmAvatarGroupCount,
HlmAvatarImage,
] as const;import { BrnAvatar, BrnAvatarFallback, BrnAvatarImage } from '@spartan-ng/brain/avatar';
import { Directive, inject, input } from '@angular/core';
import { classes } from '@spartan-ng/helm/utils';
@Directive({
selector: '[hlmAvatarBadge],hlm-avatar-badge',
exportAs: 'hlmAvatarBadge',
host: {
'data-slot': 'avatar-badge',
},
})
export class HlmAvatarBadge {
constructor() {
classes(() => [
'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>ng-icon]:hidden',
'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
]);
}
}
@Directive({
selector: '[hlmAvatarFallback]',
exportAs: 'hlmAvatarFallback',
hostDirectives: [BrnAvatarFallback],
host: {
'data-slot': 'avatar-fallback',
},
})
export class HlmAvatarFallback {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroupCount],hlm-avatar-group-count',
exportAs: 'hlmAvatarGroupCount',
host: {
'data-slot': 'avatar-group-count',
},
})
export class HlmAvatarGroupCount {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>ng-icon]:text-base group-has-data-[size=lg]/avatar-group:[&>ng-icon]:text-xl group-has-data-[size=sm]/avatar-group:[&>ng-icon]:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroup],hlm-avatar-group',
exportAs: 'hlmAvatarGroup',
host: {
'data-slot': 'avatar-group',
},
})
export class HlmAvatarGroup {
constructor() {
classes(
() => '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
);
}
}
@Directive({
selector: 'img[hlmAvatarImage]',
exportAs: 'hlmAvatarImage',
hostDirectives: [{ directive: BrnAvatarImage, inputs: ['hostStyles'] }],
host: {
'data-slot': 'avatar-image',
},
})
export class HlmAvatarImage {
public readonly canShow = inject(BrnAvatarImage).canShow;
constructor() {
classes(() => 'rounded-full aspect-square size-full object-cover');
}
}
@Directive({
selector: '[hlmAvatar],hlm-avatar',
exportAs: 'hlmAvatar',
hostDirectives: [BrnAvatar],
host: {
'data-slot': 'avatar',
'[attr.data-size]': 'size()',
},
})
export class HlmAvatar {
public readonly size = input<'default' | 'sm' | 'lg'>('default');
constructor() {
classes(
() =>
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar after:border-border relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
);
}
}
export const HlmAvatarImports = [
HlmAvatar,
HlmAvatarBadge,
HlmAvatarFallback,
HlmAvatarGroup,
HlmAvatarGroupCount,
HlmAvatarImage,
] as const;import { BrnAvatar, BrnAvatarFallback, BrnAvatarImage } from '@spartan-ng/brain/avatar';
import { Directive, inject, input } from '@angular/core';
import { classes } from '@spartan-ng/helm/utils';
@Directive({
selector: '[hlmAvatarBadge],hlm-avatar-badge',
exportAs: 'hlmAvatarBadge',
host: {
'data-slot': 'avatar-badge',
},
})
export class HlmAvatarBadge {
constructor() {
classes(() => [
'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>ng-icon]:hidden',
'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
]);
}
}
@Directive({
selector: '[hlmAvatarFallback]',
exportAs: 'hlmAvatarFallback',
hostDirectives: [BrnAvatarFallback],
host: {
'data-slot': 'avatar-fallback',
},
})
export class HlmAvatarFallback {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroupCount],hlm-avatar-group-count',
exportAs: 'hlmAvatarGroupCount',
host: {
'data-slot': 'avatar-group-count',
},
})
export class HlmAvatarGroupCount {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>ng-icon]:text-base group-has-data-[size=lg]/avatar-group:[&>ng-icon]:text-xl group-has-data-[size=sm]/avatar-group:[&>ng-icon]:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroup],hlm-avatar-group',
exportAs: 'hlmAvatarGroup',
host: {
'data-slot': 'avatar-group',
},
})
export class HlmAvatarGroup {
constructor() {
classes(
() => '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
);
}
}
@Directive({
selector: 'img[hlmAvatarImage]',
exportAs: 'hlmAvatarImage',
hostDirectives: [{ directive: BrnAvatarImage, inputs: ['hostStyles'] }],
host: {
'data-slot': 'avatar-image',
},
})
export class HlmAvatarImage {
public readonly canShow = inject(BrnAvatarImage).canShow;
constructor() {
classes(() => 'rounded-full aspect-square size-full object-cover');
}
}
@Directive({
selector: '[hlmAvatar],hlm-avatar',
exportAs: 'hlmAvatar',
hostDirectives: [BrnAvatar],
host: {
'data-slot': 'avatar',
'[attr.data-size]': 'size()',
},
})
export class HlmAvatar {
public readonly size = input<'default' | 'sm' | 'lg'>('default');
constructor() {
classes(
() =>
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar after:border-border relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
);
}
}
export const HlmAvatarImports = [
HlmAvatar,
HlmAvatarBadge,
HlmAvatarFallback,
HlmAvatarGroup,
HlmAvatarGroupCount,
HlmAvatarImage,
] as const;import { BrnAvatar, BrnAvatarFallback, BrnAvatarImage } from '@spartan-ng/brain/avatar';
import { Directive, inject, input } from '@angular/core';
import { classes } from '@spartan-ng/helm/utils';
@Directive({
selector: '[hlmAvatarBadge],hlm-avatar-badge',
exportAs: 'hlmAvatarBadge',
host: {
'data-slot': 'avatar-badge',
},
})
export class HlmAvatarBadge {
constructor() {
classes(() => [
'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>ng-icon]:hidden',
'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
]);
}
}
@Directive({
selector: '[hlmAvatarFallback]',
exportAs: 'hlmAvatarFallback',
hostDirectives: [BrnAvatarFallback],
host: {
'data-slot': 'avatar-fallback',
},
})
export class HlmAvatarFallback {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroupCount],hlm-avatar-group-count',
exportAs: 'hlmAvatarGroupCount',
host: {
'data-slot': 'avatar-group-count',
},
})
export class HlmAvatarGroupCount {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>ng-icon]:text-base group-has-data-[size=lg]/avatar-group:[&>ng-icon]:text-xl group-has-data-[size=sm]/avatar-group:[&>ng-icon]:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroup],hlm-avatar-group',
exportAs: 'hlmAvatarGroup',
host: {
'data-slot': 'avatar-group',
},
})
export class HlmAvatarGroup {
constructor() {
classes(
() => '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
);
}
}
@Directive({
selector: 'img[hlmAvatarImage]',
exportAs: 'hlmAvatarImage',
hostDirectives: [{ directive: BrnAvatarImage, inputs: ['hostStyles'] }],
host: {
'data-slot': 'avatar-image',
},
})
export class HlmAvatarImage {
public readonly canShow = inject(BrnAvatarImage).canShow;
constructor() {
classes(() => 'rounded-full aspect-square size-full object-cover');
}
}
@Directive({
selector: '[hlmAvatar],hlm-avatar',
exportAs: 'hlmAvatar',
hostDirectives: [BrnAvatar],
host: {
'data-slot': 'avatar',
'[attr.data-size]': 'size()',
},
})
export class HlmAvatar {
public readonly size = input<'default' | 'sm' | 'lg'>('default');
constructor() {
classes(
() =>
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar after:border-border relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
);
}
}
export const HlmAvatarImports = [
HlmAvatar,
HlmAvatarBadge,
HlmAvatarFallback,
HlmAvatarGroup,
HlmAvatarGroupCount,
HlmAvatarImage,
] as const;import { BrnAvatar, BrnAvatarFallback, BrnAvatarImage } from '@spartan-ng/brain/avatar';
import { Directive, inject, input } from '@angular/core';
import { classes } from '@spartan-ng/helm/utils';
@Directive({
selector: '[hlmAvatarBadge],hlm-avatar-badge',
exportAs: 'hlmAvatarBadge',
host: {
'data-slot': 'avatar-badge',
},
})
export class HlmAvatarBadge {
constructor() {
classes(() => [
'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>ng-icon]:hidden',
'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
]);
}
}
@Directive({
selector: '[hlmAvatarFallback]',
exportAs: 'hlmAvatarFallback',
hostDirectives: [BrnAvatarFallback],
host: {
'data-slot': 'avatar-fallback',
},
})
export class HlmAvatarFallback {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroupCount],hlm-avatar-group-count',
exportAs: 'hlmAvatarGroupCount',
host: {
'data-slot': 'avatar-group-count',
},
})
export class HlmAvatarGroupCount {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>ng-icon]:text-base group-has-data-[size=lg]/avatar-group:[&>ng-icon]:text-xl group-has-data-[size=sm]/avatar-group:[&>ng-icon]:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroup],hlm-avatar-group',
exportAs: 'hlmAvatarGroup',
host: {
'data-slot': 'avatar-group',
},
})
export class HlmAvatarGroup {
constructor() {
classes(
() => '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
);
}
}
@Directive({
selector: 'img[hlmAvatarImage]',
exportAs: 'hlmAvatarImage',
hostDirectives: [{ directive: BrnAvatarImage, inputs: ['hostStyles'] }],
host: {
'data-slot': 'avatar-image',
},
})
export class HlmAvatarImage {
public readonly canShow = inject(BrnAvatarImage).canShow;
constructor() {
classes(() => 'rounded-full aspect-square size-full object-cover');
}
}
@Directive({
selector: '[hlmAvatar],hlm-avatar',
exportAs: 'hlmAvatar',
hostDirectives: [BrnAvatar],
host: {
'data-slot': 'avatar',
'[attr.data-size]': 'size()',
},
})
export class HlmAvatar {
public readonly size = input<'default' | 'sm' | 'lg'>('default');
constructor() {
classes(
() =>
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar after:border-border relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
);
}
}
export const HlmAvatarImports = [
HlmAvatar,
HlmAvatarBadge,
HlmAvatarFallback,
HlmAvatarGroup,
HlmAvatarGroupCount,
HlmAvatarImage,
] as const;import { BrnAvatar, BrnAvatarFallback, BrnAvatarImage } from '@spartan-ng/brain/avatar';
import { Directive, inject, input } from '@angular/core';
import { classes } from '@spartan-ng/helm/utils';
@Directive({
selector: '[hlmAvatarBadge],hlm-avatar-badge',
exportAs: 'hlmAvatarBadge',
host: {
'data-slot': 'avatar-badge',
},
})
export class HlmAvatarBadge {
constructor() {
classes(() => [
'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none',
'group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>ng-icon]:hidden',
'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>ng-icon]:text-[length:--spacing(2)]',
]);
}
}
@Directive({
selector: '[hlmAvatarFallback]',
exportAs: 'hlmAvatarFallback',
hostDirectives: [BrnAvatarFallback],
host: {
'data-slot': 'avatar-fallback',
},
})
export class HlmAvatarFallback {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroupCount],hlm-avatar-group-count',
exportAs: 'hlmAvatarGroupCount',
host: {
'data-slot': 'avatar-group-count',
},
})
export class HlmAvatarGroupCount {
constructor() {
classes(
() =>
'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>ng-icon]:text-base group-has-data-[size=lg]/avatar-group:[&>ng-icon]:text-xl group-has-data-[size=sm]/avatar-group:[&>ng-icon]:text-xs',
);
}
}
@Directive({
selector: '[hlmAvatarGroup],hlm-avatar-group',
exportAs: 'hlmAvatarGroup',
host: {
'data-slot': 'avatar-group',
},
})
export class HlmAvatarGroup {
constructor() {
classes(
() => '*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2',
);
}
}
@Directive({
selector: 'img[hlmAvatarImage]',
exportAs: 'hlmAvatarImage',
hostDirectives: [{ directive: BrnAvatarImage, inputs: ['hostStyles'] }],
host: {
'data-slot': 'avatar-image',
},
})
export class HlmAvatarImage {
public readonly canShow = inject(BrnAvatarImage).canShow;
constructor() {
classes(() => 'rounded-full aspect-square size-full object-cover');
}
}
@Directive({
selector: '[hlmAvatar],hlm-avatar',
exportAs: 'hlmAvatar',
hostDirectives: [BrnAvatar],
host: {
'data-slot': 'avatar',
'[attr.data-size]': 'size()',
},
})
export class HlmAvatar {
public readonly size = input<'default' | 'sm' | 'lg'>('default');
constructor() {
classes(
() =>
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 group/avatar after:border-border relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
);
}
}
export const HlmAvatarImports = [
HlmAvatar,
HlmAvatarBadge,
HlmAvatarFallback,
HlmAvatarGroup,
HlmAvatarGroupCount,
HlmAvatarImage,
] as const;Usage
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';<hlm-avatar>
<img hlmAvatarImage src='/assets/avatar.png' alt='spartan logo. Resembling a spartanic shield' />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>Examples
Basic
A basic avatar component with an image and a fallback.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-basic-preview',
imports: [HlmAvatarImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
`,
})
export class AvatarBasicPreview {}NgOptimizedImage
The avatar image is rendered immediately and hidden until it loads, so Angular's NgOptimizedImage directive works with ngSrc as well as a regular src . The image is only visually hidden, not removed from the layout, so native lazy loading still loads it when it enters the viewport. Use fill to let the image fill the avatar.
import { NgOptimizedImage } from '@angular/common';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-ng-optimized-image-preview',
imports: [HlmAvatarImports, NgOptimizedImage],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-avatar>
<img hlmAvatarImage ngSrc="/assets/avatar.png" fill alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
`,
})
export class AvatarNgOptimizedImagePreview {}Badge
Use the hlm-avatar-badge component to add a badge to the avatar. The badge is positioned at the bottom right of the avatar.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-badge-preview',
imports: [HlmAvatarImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" />
<span hlmAvatarFallback>RG</span>
<hlm-avatar-badge class="bg-red-600 dark:bg-red-800" />
</hlm-avatar>
`,
})
export class AvatarBadgePreview {}Badge Icon
You can also use an icon inside hlm-avatar-badge .
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucidePlus } from '@ng-icons/lucide';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-badge-icon-preview',
imports: [HlmAvatarImports, NgIcon],
providers: [provideIcons({ lucidePlus })],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-avatar class="grayscale">
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" />
<span hlmAvatarFallback>RG</span>
<hlm-avatar-badge>
<ng-icon name="lucidePlus" />
</hlm-avatar-badge>
</hlm-avatar>
`,
})
export class AvatarBadgeIconPreview {}Avatar Group
Use the hlm-avatar-group component to add a group of avatars.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-group-preview',
imports: [HlmAvatarImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-avatar-group class="grayscale">
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
</hlm-avatar-group>
`,
})
export class AvatarGroupPreview {}Avatar Group Count
Use hlm-avatar-group-count to add a count to the group.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-group-count-preview',
imports: [HlmAvatarImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-avatar-group class="grayscale">
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar-group-count>+3</hlm-avatar-group-count>
</hlm-avatar-group>
`,
})
export class AvatarGroupCountPreview {}Avatar Group Icon
You can also use an icon inside hlm-avatar-group-count
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucidePlus } from '@ng-icons/lucide';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-group-icon-preview',
imports: [HlmAvatarImports, NgIcon],
providers: [provideIcons({ lucidePlus })],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<hlm-avatar-group class="grayscale">
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar-group-count>
<ng-icon name="lucidePlus" />
</hlm-avatar-group-count>
</hlm-avatar-group>
`,
})
export class AvatarGroupIconPreview {}Sizes
Use the size prop to change the size of the avatar.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-sizes-preview',
imports: [HlmAvatarImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex flex-wrap items-center gap-2 grayscale">
<hlm-avatar size="sm">
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar size="lg">
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" class="grayscale" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
</div>
`,
})
export class AvatarSizesPreview {}Dropdown
You can use the hlm-avatar component as trigger for a dropdown menu.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
import { HlmButtonImports } from '@spartan-ng/helm/button';
import { HlmDropdownMenuImports } from '@spartan-ng/helm/dropdown-menu';
@Component({
selector: 'spartan-avatar-dropdown-preview',
imports: [HlmAvatarImports, HlmDropdownMenuImports, HlmButtonImports],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<button hlmBtn variant="ghost" size="icon" class="rounded-full" [hlmDropdownMenuTrigger]="menu">
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="@spartan-ui logo" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
</button>
<ng-template #menu>
<hlm-dropdown-menu>
<hlm-dropdown-menu-group>
<button hlmDropdownMenuItem>Profile</button>
<button hlmDropdownMenuItem>Billing</button>
<button hlmDropdownMenuItem>Settings</button>
</hlm-dropdown-menu-group>
<hlm-dropdown-menu-separator />
<hlm-dropdown-menu-group>
<button hlmDropdownMenuItem variant="destructive">Log out</button>
</hlm-dropdown-menu-group>
</hlm-dropdown-menu>
</ng-template>
`,
})
export class AvatarDropdownPreview {}RTL
To enable RTL support in spartan-ng, see the RTL configuration guide.
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
import { TranslateService, Translations } from '@spartan-ng/app/app/shared/translate.service';
import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
@Component({
selector: 'spartan-avatar-rtl-preview',
imports: [HlmAvatarImports],
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[dir]': '_dir()',
},
template: `
<div class="flex flex-row flex-wrap items-center gap-6 md:gap-12">
<hlm-avatar>
<img
hlmAvatarImage
src="/assets/avatar.png"
alt="spartan logo. Resembling a spartanic shield"
class="grayscale"
/>
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img
hlmAvatarImage
src="/assets/avatar.png"
alt="spartan logo. Resembling a spartanic shield"
class="grayscale"
/>
<span hlmAvatarFallback>RG</span>
<hlm-avatar-badge class="bg-red-600 dark:bg-red-800" />
</hlm-avatar>
<hlm-avatar-group class="grayscale">
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="spartan logo. Resembling a spartanic shield" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="spartan logo. Resembling a spartanic shield" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar>
<img hlmAvatarImage src="/assets/avatar.png" alt="spartan logo. Resembling a spartanic shield" />
<span hlmAvatarFallback>RG</span>
</hlm-avatar>
<hlm-avatar-group-count>{{ _t()['moreUsers'] }}</hlm-avatar-group-count>
</hlm-avatar-group>
</div>
`,
})
export class AvatarRtlPreview {
private readonly _language = inject(TranslateService).language;
private readonly _translations: Translations = {
en: {
dir: 'ltr',
values: {
moreUsers: '+3',
},
},
ar: {
dir: 'rtl',
values: {
moreUsers: '+٣',
},
},
he: {
dir: 'rtl',
values: {
moreUsers: '+3',
},
},
};
private readonly _translation = computed(() => this._translations[this._language()]);
protected readonly _t = computed(() => this._translation().values);
protected readonly _dir = computed(() => this._translation().dir);
}Brain API
BrnAvatar
Selector: [brnAvatar],brn-avatar
ExportAs: brnAvatar
BrnAvatarFallback
Selector: [brnAvatarFallback]
ExportAs: brnAvatarFallback
BrnAvatarImage
Selector: img[brnAvatarImage]
ExportAs: brnAvatarImage
Inputs
| Prop | Type | Default | Description |
|---|---|---|---|
| hostStyles | string | 'position: absolute; inset: 0; width: 100%; height: 100%' | Styles applied to the host element. Bound via `[style]` so they apply through the CSSOM and are not blocked by a strict Content-Security-Policy. |
Helm API
HlmAvatarBadge
Selector: [hlmAvatarBadge],hlm-avatar-badge
ExportAs: hlmAvatarBadge
HlmAvatarFallback
Selector: [hlmAvatarFallback]
ExportAs: hlmAvatarFallback
HlmAvatarGroupCount
Selector: [hlmAvatarGroupCount],hlm-avatar-group-count
ExportAs: hlmAvatarGroupCount
HlmAvatarGroup
Selector: [hlmAvatarGroup],hlm-avatar-group
ExportAs: hlmAvatarGroup
HlmAvatarImage
Selector: img[hlmAvatarImage]
ExportAs: hlmAvatarImage
Inputs
| Prop | Type | Default | Description |
|---|---|---|---|
| hostStyles | string | 'position: absolute; inset: 0; width: 100%; height: 100%' | Styles applied to the host element. Bound via `[style]` so they apply through the CSSOM and are not blocked by a strict Content-Security-Policy. |
HlmAvatar
Selector: [hlmAvatar],hlm-avatar
ExportAs: hlmAvatar
Inputs
| Prop | Type | Default | Description |
|---|---|---|---|
| size | 'default' | 'sm' | 'lg' | 'default' | - |
On This Page