Animated icons
Create animated SVG icons — micro-interactions where the icon itself moves. Use when the user wants to animate an icon or add icon-level feedback, hover effects on icon buttons, hamburger menu morph to X, chevron rotate, heart/bookmark/like fill pop, play/pause swap, copy-to-clipboard checkmark, success check draw-on, loading spinner, download progress, bell ring, send/paper-plane liftoff, sun-moon theme toggle, or trash-can hover affordance. Triggers on "animate this icon", "icon animation", "animated hamburger", "like button animation", "checkmark animation", "make the icon react", "micro-interaction". Also use when adding hover/click feedback to any button whose visual is an icon, even if the user doesn't say "animate".From its SKILL.md
npx -y skills add AdzeB/animated-icons --skill animated-iconsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.5 KB, ~3.2k tokens by cl100k_base, as published. Nobody here has run it
Animated Icons
Twelve production-ready SVG icon animations, each namespaced under ic-* selectors with shared --icon-* motion tokens. Every recipe is inline SVG + CSS, with a small JS trigger where state changes require one. No libraries, no build step, and every snippet ships a prefers-reduced-motion guard.
An animated icon is not decoration — it's the interface answering the user. The icon confirms an action landed (copy → check), shows system state (spinner, progress ring), reveals what a control will do (trash lid opens on hover), or tracks a state change the user caused (hamburger → X). If the motion doesn't communicate one of those things, the icon shouldn't animate.
Quick reference
| Recipe | When to use | Reference |
|---|---|---|
| Hamburger → X | Nav trigger that morphs between menu-closed and menu-open. | 01-hamburger-x.md |
| Chevron rotate | Disclosure indicator that flips when a section expands. | 02-chevron-rotate.md |
| Fill pop | Heart/bookmark/star toggle whose fill pops in with a bounce. | 03-fill-pop.md |
| Glyph swap | Two icons sharing one slot (play ↔ pause, mute ↔ unmute). | 04-glyph-swap.md |
| Copy → check | Click feedback that shows a check, then auto-reverts. | 05-copy-check.md |
| Success check draw | Checkmark that draws itself on for a "done" moment. | 06-success-check-draw.md |
| Spinner | Indeterminate loading arc, loops only while loading. | 07-spinner.md |
| Download progress | Arrow → progress ring → check, driven by real progress. | 08-download-progress.md |
| Bell ring | One-shot ring when a notification arrives. | 09-bell-ring.md |
| Send liftoff | Paper plane flies out and returns after a message sends. | 10-send-liftoff.md |
| Sun ↔ moon | Theme toggle: rays retract, a mask carves the crescent. | 11-sun-moon.md |
| Trash lid | Lid tips open on hover — affordance before a destructive act. | 12-trash-lid.md |
Decision rules
Match the user's intent against what the icon must communicate, then pick the recipe:
- Menu open/close trigger → hamburger → X.
- Expand/collapse indicator (accordion, dropdown arrow, "show more") → chevron rotate.
- On/off toggle the user owns (like, save, bookmark, favorite, star) → fill pop.
- Two mutually exclusive glyphs in one slot (play/pause, mute/unmute, lock/unlock, eye/eye-off) → glyph swap.
- Click feedback that must revert on its own (copy link, copy code, add to cart flash) → copy → check.
- A "done" celebration (form submitted, payment complete, upload finished) → success check draw.
- Waiting, duration unknown → spinner.
- Waiting, progress known (download, upload, export) → download progress.
- Something arrived (new notification, new message badge moment) → bell ring.
- Something departed (message sent, email sent) → send liftoff.
- Light/dark mode switch → sun ↔ moon.
- Hover affordance on a destructive control (delete, remove) → trash lid.
- No clear match → compose from the technique primitives below rather than forcing a recipe. The primitives cover most novel icons.
If two fit, prefer the quieter one — glyph swap over a bespoke morph, fill pop over success check. The user sees icon animations many times a day; restraint is what keeps them likable.
Should this icon animate at all?
Frequency decides. An icon triggered 100+ times a day (keyboard-driven toggles, tab switches) should not animate — motion there reads as latency. An icon seen occasionally (menu toggle, save, send) earns a standard 150–250ms animation. A rare moment (payment success, onboarding complete) can afford a 400–600ms celebration. Never animate an icon purely because it can move; looping decorative motion next to content becomes noise the third time the user sees it.
Timing and easing for icons
Icons are small, so motion reads faster than on large surfaces — keep durations at the short end:
| Moment | Duration | Easing |
|---|---|---|
Press feedback (scale on :active) | 100–150ms | --icon-ease-out |
| State toggle (burger, chevron, glyph swap, fill pop) | 150–250ms | --icon-ease-in-out, --icon-ease-bounce for pops |
| Stroke draw-on | 300–400ms | --icon-ease-out |
| Celebration / multi-stage | 400–600ms total | staged, see recipe |
| Loops (spinner) | 700–900ms per revolution | linear |
Use transitions, not keyframes, for anything toggleable. A user who taps a hamburger twice quickly should see the lines retarget smoothly mid-flight; keyframes restart from zero and stutter. Reserve keyframes for one-shot effects (bell ring, send liftoff) and loops (spinner), and replay one-shots by removing the class, forcing a reflow (void el.offsetWidth), and re-adding it.
SVG technique primitives
These five techniques compose into almost any icon animation. The recipes are worked examples of them.
1. Transform sub-elements — set transform-box
The workhorse: rotate, translate, and scale the icon's parts (<g>, <rect>, <path>), not just the whole icon. The one thing that breaks everyone: SVG child elements rotate around the viewBox origin by default, not their own center. Always pair transforms on SVG children with:
.ic-part {
transform-box: fill-box; /* origin box = the element's own bounds */
transform-origin: center; /* now "center" means the part's center */
}
Transforms on the <svg> element itself (or the button) are ordinary HTML transforms — no transform-box needed. Prefer <rect>/<path> with real area over <line> when a part will be transformed; zero-height geometry gives some engines a degenerate fill-box.
2. Stroke draw — pathLength="1"
Setting pathLength="1" on a path normalizes its length to 1, so draw-on animations need no getTotalLength() measurement and never desync from the actual geometry:
<path d="M7 12.5l3.5 3.5L17 9" pathLength="1" ... />
path { stroke-dasharray: 1; stroke-dashoffset: 1; } /* hidden */
.is-shown path { stroke-dashoffset: 0; transition: stroke-dashoffset 350ms var(--icon-ease-out); }
Offset 1 → 0 draws the stroke on; 0 → 1 erases it. Works for checkmarks, progress rings (stroke-dashoffset: calc(1 - var(--progress))), and underline effects.
3. Crossfade two glyphs in one slot
Path morphing between arbitrary shapes is fragile — CSS d: interpolation is Chromium-only and requires identical command structure, so it silently fails on Safari and Firefox. The robust equivalent: stack both glyphs in one SVG, and transition opacity + scale (+ 2px blur to mask the overlap) between them. The blur makes two objects read as one transforming object. See glyph swap for the canonical snippet. True morphs are worth it only when the shapes share structure — the hamburger → X "morph" is really three rects rotating, which is why it works everywhere.
4. Masks for shape carving
When a shape must gain or lose a bite (moon crescent, cutout effects), animate a transform on a <circle> inside a <mask> instead of morphing the path. Mask contents accept CSS transforms in all modern browsers.
5. currentColor everywhere
Every recipe uses stroke="currentColor" / fill="currentColor" so the icon inherits text color and state color changes (like the fill pop turning red) are a single color: transition on the button.
Performance and accessibility
- Animate only
transform,opacity,stroke-dashoffset, and shortfilter: blur()s. Never animatewidth,height,x,y, orstroke-width— they trigger layout or repaint per frame. - Icons are tiny; even so,
transition: allis banned. Enumerate properties so an unrelated style change (a theme swap, a hover color) doesn't ride the transition. - Every snippet ships a
@media (prefers-reduced-motion: reduce)block. Reduced motion means gentler, not broken: keep opacity crossfades and color changes (state must still be communicated), remove rotation, travel, and bounce. A spinner must still indicate loading — the guard swaps the rotation for a soft opacity pulse. - State lives in semantics, not classes, wherever a real state exists:
aria-expandedon the hamburger and chevron triggers,aria-pressedon toggles (fill pop, sun/moon). CSS selects on those attributes, so the animation and the accessibility tree can never disagree. - Hover-only effects (trash lid) are gated behind
@media (hover: hover) and (pointer: fine)— touch devices fire sticky hovers on tap. - Loops end. A spinner stops when loading resolves; nothing loops for decoration.
Working with an existing icon set (Lucide, Nucleo, Heroicons…)
Recipes ship with generic 24×24 geometry, but users usually want their icon animated. The procedure:
- Inline the SVG into the markup (animating parts requires DOM access —
<img src>and icon fonts can't do this). - Split it into logical parts: wrap the pieces that move in
<g>elements or target existing paths, and add the recipe'sic-*classes. A trash icon becomes.ic-trash-lid(lid + handle paths) and the body; a bell becomes the bell shape + clapper. - Keep the set's own
stroke-width,stroke-linecap, andviewBox— visual consistency with the user's other icons matters more than matching the recipe's sample geometry. - Re-point any hardcoded coordinates in the recipe (translate distances, transform-origins given in viewBox units) at the actual geometry.
Output format
When installing a recipe into a user's project:
- Install
_tokens.cssonce into the global stylesheet (or paste its:rootblock). Skip if the--icon-*tokens already exist. Per-recipe files restate only the tunables they need, so a single recipe can also go in standalone. - Paste the recipe's SVG and CSS, adapting geometry to the user's icon set per the section above. Keep the class names, state attributes, and the enumerated transition properties.
- Wire the state hooks —
aria-expanded,aria-pressed,data-state, or the.is-*class the recipe documents — from whatever state the user's app already has. Don't invent parallel state. - Keep the
prefers-reduced-motionblock. Removing it fails accessibility audits. - Copy the JS trigger where the recipe has one (copy → check, success draw replay, bell replay, download progress) and adapt selectors to the user's DOM.
Keep the diff small: only the files needed for the icon. Don't pull in a motion library for anything in this catalog.
Common mistakes
- Transforming an SVG child without
transform-box: fill-box— the part orbits the viewBox origin instead of rotating in place. This is the #1 icon animation bug. - Animating
d:path morphs — Chromium-only; Safari and Firefox users see a hard cut. Use crossfade (technique 3) or structural transforms instead. - Keyframes on a toggle — double-tapping restarts the animation from zero. Toggles use transitions; one-shots use keyframes with the reflow-replay pattern.
- Hardcoding
stroke-dasharrayto a measured length — drifts when the path is edited. UsepathLength="1". scale(0)starts — nothing real appears from nothing. Pops start atscale(0.4)+opacity: 0or higher.- Forgetting the reflow between class-remove and class-re-add — one-shot animations (bell, success draw, send) won't replay without
void el.offsetWidth. - An endless decorative loop — a bell that never stops swinging or a pulsing dot that outlives its message trains users to ignore the region entirely (and violates WCAG pause/stop/hide).
- Icon animates but state doesn't — wiring the animation to a bespoke class while
aria-expandedstays stale. Select on the semantic attribute so they can't diverge.
Reference files
- 01-hamburger-x.md — Hamburger → X
- 02-chevron-rotate.md — Chevron rotate
- 03-fill-pop.md — Fill pop (heart/bookmark/star)
- 04-glyph-swap.md — Glyph swap (play/pause)
- 05-copy-check.md — Copy → check
- 06-success-check-draw.md — Success check draw
- 07-spinner.md — Spinner
- 08-download-progress.md — Download progress
- 09-bell-ring.md — Bell ring
- 10-send-liftoff.md — Send liftoff
- 11-sun-moon.md — Sun ↔ moon
- 12-trash-lid.md — Trash lid
- _tokens.css — shared motion tokens, import once
What ships with it: 13 files
29.6 KB alongside SKILL.md
- 01-hamburger-x.md2.3 KB
- 02-chevron-rotate.md1.2 KB
- 03-fill-pop.md2.5 KB
- 04-glyph-swap.md2.4 KB
- 05-copy-check.md2.8 KB
- 06-success-check-draw.md2.1 KB
- 07-spinner.md1.6 KB
- 08-download-progress.md3.4 KB
- 09-bell-ring.md2.4 KB
- 10-send-liftoff.md2.5 KB
- 11-sun-moon.md3.2 KB
- 12-trash-lid.md2.1 KB
- _tokens.css1.2 KB