Getting started
Animated Lucide icons on native Web Animations API. Zero animation dependencies at runtime — no @angular/animations, no Motion, no GSAP.
Install
npm i glyphflowAngular 20, 21, or 22 (peerDependencies: >=20.0.0 <23.0.0). The only real dependency is tslib. MIT licensed.
Your first icon
The component is standalone: import it where you use it, no intermediate module. The icon is passed as a value, via a named import.
import { Component } from '@angular/core';
import { GfIconComponent, bellIcon } from 'glyphflow';
@Component({
selector: 'app-alert',
imports: [GfIconComponent],
template: '<gf-icon [iconDef]="bell" [size]="24" label="Notifications" />',
})
export class Alert {
protected readonly bell = bellIcon;
}Why [iconDef] and not name=
Because it's the path that actually tree-shakes. The numbers come from the budget that runs in CI on every commit, not a promise:
| What you import | Bundle (gzip) |
|---|---|
| Just the component, no icon at all | 5.24 KB |
| Component + 1 icon via named import recommended | 5.6 KB |
name= with the full registry | 174.74 KB |
An icon costs that icon. Looking one up by name at runtime forces the bundler to keep all 1767, because it can't know which one you'll ask for.
If you still want name=
It exists, and it's opt-in on purpose: without registering the catalog, name="bell" renders nothing and doesn't throw — same contract as an unknown name.
import { provideIconCatalog, ANIMATED_ICONS } from 'glyphflow';
bootstrapApplication(App, {
providers: [provideIconCatalog(ANIMATED_ICONS)],
});When it animates
trigger | What it does |
|---|---|
group | Draws on mount and animates with the hover of the nearest .group container — the same one Tailwind's group-hover: uses. If there's none, it listens to the icon itself. |
hover | On pointer enter. |
tap | On click. |
view | On entering the viewport. viewOnce decides once vs. every re-entry. |
auto | As soon as it mounts. |
manual | Never on its own: you trigger it by calling play() on the instance. |
Global speed
One multiplier for every computed duration — choreography and morph transitions alike. 1 means no change; without providing anything, behavior stays untouched.
import { provideGfIcons } from 'glyphflow';
providers: [provideGfIcons({ durationScale: 0.8 })]The speed chips in this site's top bar are exactly that, live. They apply starting with the next playback: whatever's already running keeps the duration the browser already gave it.
Morphing between two icons
Lives in a separate entry point, glyphflow/morph, so that anyone who only wants animated icons doesn't pay for shape-interpolation math.
import { GfIconMorphComponent } from 'glyphflow/morph';
// The binding IS the state: changing [icon] triggers the transition from the previous value.
// <gf-icon-morph [icon]="current()" [size]="32" label="Status" />The first value renders static — morphing "from nothing" doesn't exist. SSR and any browser without WAAPI fall into the same case: you see the icon, it just doesn't animate.
[spring] decides what the transition feels like: smooth (default, no overshoot), snappy (fast, subtle overshoot), or bouncy (overshoots the target and settles back). The spring never touches the geometry — only the duration and how keyframes are spaced out over time. It also accepts your own { k, c } if you want to tune it yourself.