Run the CLI
Use the CLI to add the component to your project.
npx @ngzard/ui add iconA versatile icon component that encapsulates lucide-angular's icons with a consistent API and styling, providing an abstraction layer that facilitates future icon library swapping.
import { Component } from '@angular/core';
import { ZardIconComponent } from '../icon.component';
@Component({
selector: 'z-demo-icon-default',
standalone: true,
imports: [ZardIconComponent],
template: `
<div class="flex items-center gap-4">
<z-icon zType="house" />
<z-icon zType="settings" />
<z-icon zType="user" />
<z-icon zType="search" />
<z-icon zType="bell" />
<z-icon zType="mail" />
</div>
`,
})
export class ZardDemoIconDefaultComponent {}
Use the CLI to add the component to your project.
npx @ngzard/ui add iconpnpm dlx @ngzard/ui add iconyarn dlx @ngzard/ui add iconbunx @ngzard/ui add iconCreate the component directory structure and add the following files to your project.
import { ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation } from '@angular/core';
import { LucideAngularModule } from 'lucide-angular';
import type { ClassValue } from 'clsx';
import { iconVariants, type ZardIconVariants } from './icon.variants';
import { mergeClasses } from '../../shared/utils/utils';
import { ZARD_ICONS, type ZardIcon } from './icons';
@Component({
selector: 'z-icon, [z-icon]',
standalone: true,
imports: [LucideAngularModule],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
template: ` <lucide-angular [img]="icon()" [strokeWidth]="zStrokeWidth()" [absoluteStrokeWidth]="zAbsoluteStrokeWidth()" [class]="classes()" /> `,
host: {},
})
export class ZardIconComponent {
readonly zType = input.required<ZardIcon>();
readonly zSize = input<ZardIconVariants['zSize']>('default');
readonly zStrokeWidth = input<number>(2);
readonly zAbsoluteStrokeWidth = input<boolean>(false);
readonly class = input<ClassValue>('');
protected readonly classes = computed(() => mergeClasses(iconVariants({ zSize: this.zSize() }), this.class()));
protected readonly icon = computed(() => {
const type = this.zType();
if (typeof type === 'string') {
return ZARD_ICONS[type as keyof typeof ZARD_ICONS];
}
return type;
});
}
import { cva, type VariantProps } from 'class-variance-authority';
export const iconVariants = cva('flex items-center justify-center', {
variants: {
zSize: {
sm: 'size-3',
default: 'size-3.5',
lg: 'size-4',
xl: 'size-5',
},
},
defaultVariants: {
zSize: 'default',
},
});
export type ZardIconVariants = VariantProps<typeof iconVariants>;
import {
Archive,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowUpRight,
BadgeCheck,
Ban,
Bell,
Bold,
BookOpen,
BookOpenText,
Calendar,
CalendarPlus,
Check,
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsUpDown,
ChevronUp,
Circle,
CircleAlert,
CircleCheck,
CircleDollarSign,
CircleX,
Clipboard,
Clock,
Code,
CodeXml,
Copy,
Ellipsis,
Eye,
File,
FileText,
Folder,
FolderCode,
FolderOpen,
FolderPlus,
Heart,
House,
Inbox,
Info,
Italic,
Layers,
Layers2,
LayoutDashboard,
Lightbulb,
LightbulbOff,
ListFilterPlus,
LoaderCircle,
LogOut,
type LucideIconData,
Mail,
Minus,
Monitor,
Moon,
MoveRight,
Palette,
PanelLeft,
Plus,
Popcorn,
Puzzle,
Save,
Search,
Settings,
Shield,
Smartphone,
Sparkles,
SquareLibrary,
Star,
Sun,
Tablet,
Tag,
Terminal,
TextAlignCenter,
TextAlignEnd,
TextAlignStart,
Trash2,
TriangleAlert,
Underline,
User,
Users,
X,
Zap,
} from 'lucide-angular';
export const ZARD_ICONS = {
house: House,
settings: Settings,
user: User,
search: Search,
bell: Bell,
mail: Mail,
calendar: Calendar,
'log-out': LogOut,
'panel-left': PanelLeft,
bold: Bold,
inbox: Inbox,
italic: Italic,
underline: Underline,
'text-align-center': TextAlignCenter,
'text-align-end': TextAlignEnd,
'text-align-start': TextAlignStart,
check: Check,
x: X,
info: Info,
'triangle-alert': TriangleAlert,
circle: Circle,
'circle-alert': CircleAlert,
'circle-check': CircleCheck,
'circle-x': CircleX,
'circle-dollar-sign': CircleDollarSign,
ban: Ban,
'chevron-down': ChevronDown,
'chevron-up': ChevronUp,
'chevron-left': ChevronLeft,
'chevron-right': ChevronRight,
'chevrons-up-down': ChevronsUpDown,
'move-right': MoveRight,
'arrow-right': ArrowRight,
'arrow-up': ArrowUp,
'arrow-up-right': ArrowUpRight,
folder: Folder,
'folder-open': FolderOpen,
'folder-plus': FolderPlus,
file: File,
'file-text': FileText,
'layout-dashboard': LayoutDashboard,
'loader-circle': LoaderCircle,
save: Save,
copy: Copy,
eye: Eye,
ellipsis: Ellipsis,
terminal: Terminal,
clipboard: Clipboard,
moon: Moon,
sun: Sun,
lightbulb: Lightbulb,
'lightbulb-off': LightbulbOff,
palette: Palette,
sparkles: Sparkles,
heart: Heart,
star: Star,
zap: Zap,
popcorn: Popcorn,
shield: Shield,
puzzle: Puzzle,
layers: Layers,
'layers-2': Layers2,
'square-library': SquareLibrary,
code: Code,
'code-xml': CodeXml,
'book-open': BookOpen,
'book-open-text': BookOpenText,
users: Users,
monitor: Monitor,
smartphone: Smartphone,
tablet: Tablet,
'badge-check': BadgeCheck,
'folder-code': FolderCode,
plus: Plus,
minus: Minus,
'arrow-left': ArrowLeft,
archive: Archive,
clock: Clock,
'calendar-plus': CalendarPlus,
'list-filter-plus': ListFilterPlus,
trash: Trash2,
tag: Tag,
} as const satisfies Record<string, LucideIconData>;
export declare type ZardIcon = keyof typeof ZARD_ICONS | LucideIconData;
import { Component } from '@angular/core';
import { ZardIconComponent } from '../icon.component';
@Component({
selector: 'z-demo-icon-default',
standalone: true,
imports: [ZardIconComponent],
template: `
<div class="flex items-center gap-4">
<z-icon zType="house" />
<z-icon zType="settings" />
<z-icon zType="user" />
<z-icon zType="search" />
<z-icon zType="bell" />
<z-icon zType="mail" />
</div>
`,
})
export class ZardDemoIconDefaultComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../icon.component';
@Component({
selector: 'z-demo-icon-sizes',
standalone: true,
imports: [ZardIconComponent],
template: `
<div class="flex items-center gap-6">
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" zSize="sm" />
<span class="text-muted-foreground text-xs">Small</span>
</div>
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" zSize="default" />
<span class="text-muted-foreground text-xs">Default</span>
</div>
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" zSize="lg" />
<span class="text-muted-foreground text-xs">Large</span>
</div>
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" zSize="xl" />
<span class="text-muted-foreground text-xs">Extra Large</span>
</div>
</div>
`,
})
export class ZardDemoIconSizesComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../icon.component';
@Component({
selector: 'z-demo-icon-colors',
standalone: true,
imports: [ZardIconComponent],
template: `
<div class="flex items-center gap-4">
<z-icon zType="heart" class="text-destructive" />
<z-icon zType="circle-check" class="text-green-500" />
<z-icon zType="triangle-alert" class="text-warning" />
<z-icon zType="info" class="text-blue-500" />
<z-icon zType="star" class="text-yellow-500" />
<z-icon zType="zap" class="text-purple-500" />
</div>
`,
})
export class ZardDemoIconColorsComponent {}
import { Component } from '@angular/core';
import { ZardIconComponent } from '../icon.component';
@Component({
selector: 'z-demo-icon-stroke-width',
standalone: true,
imports: [ZardIconComponent],
template: `
<div class="flex items-center gap-6">
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" [zStrokeWidth]="1" />
<span class="text-muted-foreground text-xs">Stroke 1</span>
</div>
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" [zStrokeWidth]="2" />
<span class="text-muted-foreground text-xs">Stroke 2</span>
</div>
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" [zStrokeWidth]="3" />
<span class="text-muted-foreground text-xs">Stroke 3</span>
</div>
<div class="flex flex-col items-center gap-2">
<z-icon zType="house" [zStrokeWidth]="4" />
<span class="text-muted-foreground text-xs">Stroke 4</span>
</div>
</div>
`,
})
export class ZardDemoIconStrokeWidthComponent {}
import { CommonModule } from '@angular/common';
import { Component, computed, signal } from '@angular/core';
import { toast } from 'ngx-sonner';
import { ZardButtonComponent } from '../../button/button.component';
import { ZardEmptyComponent } from '../../empty/empty.component';
import { ZardInputDirective } from '../../input/input.directive';
import { ZardIconComponent } from '../icon.component';
import { ZARD_ICONS } from '../icons';
@Component({
selector: 'z-demo-icon-searchable',
standalone: true,
imports: [CommonModule, ZardIconComponent, ZardInputDirective, ZardButtonComponent, ZardEmptyComponent],
template: `
<div class="flex w-full flex-col gap-4">
<div class="flex flex-col gap-2">
<div class="relative">
<input z-input type="text" placeholder="Search icons..." [value]="searchQuery()" (input)="onSearchChange($event)" class="w-full" />
<z-icon zType="search" class="text-muted-foreground pointer-events-none absolute top-1/2 right-3 -translate-y-1/2" />
</div>
<div class="text-muted-foreground text-xs leading-relaxed">
<strong>Note:</strong> These are only the icons currently used in our documentation.
<br />
For the complete icon library, visit
<a href="https://lucide.dev/icons" target="_blank" rel="noopener noreferrer" class="hover:text-foreground underline transition-colors">lucide.dev/icons.</a>
</div>
</div>
<div class="text-muted-foreground text-sm">{{ filteredIcons().length }} of {{ totalIcons }} icons</div>
<div class="grid max-h-[600px] grid-cols-2 gap-4 overflow-y-auto pr-4 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-4">
@for (iconName of filteredIcons(); track iconName) {
<button
z-button
zType="outline"
(click)="copyIconCode(iconName)"
class="group flex h-auto min-h-[70px] w-full flex-col items-center justify-center gap-2 px-3 py-2"
[title]="'Click to copy: <z-icon zType="' + iconName + '" />'"
>
<z-icon [zType]="iconName" class="shrink-0 transition-transform group-hover:scale-110" />
<span class="group-hover:text-foreground w-full text-center text-xs leading-relaxed break-words hyphens-auto transition-colors">
{{ iconName }}
</span>
</button>
}
</div>
@if (filteredIcons().length === 0) {
<z-empty zDescription="No icons found for the given search." />
}
</div>
`,
})
export class ZardDemoIconSearchableComponent {
readonly searchQuery = signal('');
readonly iconNames = Object.keys(ZARD_ICONS) as Array<keyof typeof ZARD_ICONS>;
readonly totalIcons = this.iconNames.length;
readonly filteredIcons = computed(() => {
const query = this.searchQuery().toLowerCase().trim();
if (!query) {
return this.iconNames;
}
return this.iconNames.filter(iconName => iconName.toLowerCase().includes(query));
});
onSearchChange(event: Event): void {
const input = event.target as HTMLInputElement;
this.searchQuery.set(input.value);
}
async copyIconCode(iconName: string): Promise<void> {
const code = `<z-icon zType="${iconName}" />`;
try {
await navigator.clipboard.writeText(code);
toast.success('Icon copied!', {
description: `<z-icon zType="${iconName}" />`,
duration: 2000,
});
} catch (err) {
console.error('Failed to copy:', err);
toast.error('Failed to copy', {
description: 'Could not copy to clipboard',
duration: 2000,
});
}
}
}