Skip to content
Nana UI

Animated Icon

A motion wrapper around Icon. It renders a real <nana-icon> — inheriting the entire built-in glyph set, the shared icon registry and the accessibility wiring — and layers a CSS-driven animation on top, started by a trigger.

Because it composes the existing Icon component, any name you can render with <nana-icon> — including glyphs you register yourself — works here too. It also honours prefers-reduced-motion: looping motion is suspended, and deliberate hover/click/manual motion is slowed.

Terminal window
npm install @nana-tec/ui-components
import "@nana-tec/ui-components/animated-icon";
<nana-animated-icon name="settings" animation="spin" trigger="loop"></nana-animated-icon>
<nana-animated-icon name="favorite" animation="beat"></nana-animated-icon>
<nana-animated-icon name="notifications" animation="shake" trigger="click"></nana-animated-icon>

Ten built-in presets. Looping here so the gallery is self-demonstrating.

Control when the animation runs.

<nana-animated-icon name="settings" animation="spin" trigger="loop"></nana-animated-icon>
<nana-animated-icon name="favorite" animation="beat" trigger="hover"></nana-animated-icon>
<nana-animated-icon name="notifications" animation="tada" trigger="click"></nana-animated-icon>
<nana-animated-icon id="loader" name="refresh" animation="spin" trigger="manual"></nana-animated-icon>

With trigger="manual" you drive motion from JavaScript using play(), pause() and toggle(), and react to the nana-animation-start / nana-animation-end events.

const el = document.getElementById("loader");
el.play(); // start (restarts cleanly if already running)
el.pause(); // stop and reset to the resting frame
el.toggle(); // play if paused, pause if playing
el.addEventListener("nana-animation-start", () => console.log("started"));
el.addEventListener("nana-animation-end", () => console.log("one cycle done"));

Six ways to make it yours — from a quick colour swap to a fully bespoke animation.

Colour follows currentColor, so set color. Size via the size preset or any font-size.

<nana-animated-icon name="favorite" animation="beat"
style="color: var(--nana-color-danger-600);"></nana-animated-icon>
<nana-animated-icon name="settings" animation="spin" size="xl"></nana-animated-icon>
<nana-animated-icon name="settings" animation="spin"
style="font-size: 3rem;"></nana-animated-icon>

Pick a preset with speed, or set an exact duration with --nana-animated-icon-duration.

<nana-animated-icon name="settings" animation="spin" speed="fast"></nana-animated-icon>
<nana-animated-icon name="settings" animation="spin"
style="--nana-animated-icon-duration: 4s;"></nana-animated-icon>

It shares the Icon registry — register a glyph once and animate it like any built-in.

import { registerIcons } from "@nana-tec/ui-components";
// Same registry the built-in icons use — register once, animate anywhere.
registerIcons({
rocket: { viewBox: "0 0 24 24", paths: ["M12 2C8 6 8 10 8 14l4 4 4-4c0-4 0-8-4-12z"] },
});
<nana-animated-icon name="rocket" animation="float"></nana-animated-icon>

No registration needed for a one-off — drop SVG into the default slot.

<nana-animated-icon animation="spin" trigger="loop">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5z"/>
</svg>
</nana-animated-icon>

Need a motion that isn’t built in? Define your own @keyframes and apply them to the exposed ::part(icon).

<nana-animated-icon class="bell" name="notifications"></nana-animated-icon>
@keyframes swing {
20% { transform: rotate(15deg); }
40% { transform: rotate(-10deg); }
60% { transform: rotate(5deg); }
80% { transform: rotate(-5deg); }
100% { transform: rotate(0deg); }
}
.bell::part(icon) {
animation: swing 1s ease-in-out infinite;
transform-origin: top center;
}

It’s just an inline element that inherits colour — slot it into buttons, menus, alerts, anywhere.

<nana-button variant="primary">
<nana-animated-icon name="refresh" animation="spin" trigger="hover" slot="prefix"></nana-animated-icon>
Sync
</nana-button>

Pass label for a meaningful icon — it forwards to the inner icon’s aria-label. Leave it empty for decorative icons, which are marked aria-hidden.

prefers-reduced-motion is honoured automatically: loop motion is suspended, and deliberate hover/click/manual motion is slowed to a calm 2s.

PropertyTypeDefaultDescription
nameIconName''Named icon from the built-in set (forwarded to <nana-icon>)
animation'none' | 'spin' | 'spin-reverse' | 'pulse' | 'beat' | 'bounce' | 'shake' | 'flip' | 'wobble' | 'float' | 'tada''pulse'Motion preset
trigger'loop' | 'hover' | 'click' | 'manual''hover'What starts the animation
size'xs' | 'sm' | 'md' | 'lg' | 'xl''md'Size preset
speed'slow' | 'normal' | 'fast''normal'Animation speed (mapped to a duration)
labelstring''Accessible label. Empty marks the icon as decorative.
MethodDescription
play()Start the animation; restarts cleanly if already running.
pause()Stop and reset to the resting frame.
toggle()Play if paused, pause if playing.
EventDescription
nana-animation-startFired when a play cycle begins.
nana-animation-endFired when a one-shot (click/manual) cycle ends.
NameKindDescription
--nana-animated-icon-durationcustom propertyOverride the animation duration
icon::partThe composed <nana-icon> — target it to apply a fully custom animation
(default)slotCustom SVG content, used when name is not set
Explore Animated Icon interactively Every state, prop, and edge case — with live controls — in Storybook.
View in Storybook