@sudoh/animated-icons
v0.1.3
Published
Playful, spring-animated SVG icons for React
Readme
@sudoh/animated-icons
Playful, spring-animated SVG icons for React. Each icon responds to hover (or click) with a satisfying spring-physics "boop" animation powered by react-spring. Respects prefers-reduced-motion out of the box.
Install
npm install @sudoh/animated-icons react react-dom react-springreact, react-dom, and react-spring are peer dependencies — bring your own versions (React 18+ and react-spring 9+ are supported).
Quick start
import { IconSearch, IconMoon, IconGitHub } from '@sudoh/animated-icons';
function App() {
return (
<nav>
<IconSearch />
<IconMoon size={24} />
<IconGitHub size={32} />
</nav>
);
}Hover over any icon and it springs to life. That's it.
Props
Every icon accepts these props:
<IconSearch
size={24} // icon size in px (default: 30)
boopTiming={200} // how long the boop stays active in ms
/>Icons are wrapped in a <span> so they also accept standard HTML span attributes like className, style, onClick, etc.
Examples
Basic usage
import { IconArrowRight } from '@sudoh/animated-icons';
function ReadMoreLink() {
return (
<a href="/blog" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
Read more <IconArrowRight size={20} />
</a>
);
}Color mode toggle
IconColorMode is a special icon that toggles between sun and moon on click:
import { IconColorMode } from '@sudoh/animated-icons';
function Header() {
return (
<button aria-label="Toggle color mode" style={{ background: 'none', border: 'none' }}>
<IconColorMode size={28} />
</button>
);
}Social links
import { IconGitHub, IconLinkedIn, IconRSS } from '@sudoh/animated-icons';
function SocialLinks() {
return (
<div style={{ display: 'flex', gap: 16 }}>
<a href="https://github.com" aria-label="GitHub">
<IconGitHub size={24} />
</a>
<a href="https://linkedin.com" aria-label="LinkedIn">
<IconLinkedIn size={24} />
</a>
<a href="/rss.xml" aria-label="RSS Feed">
<IconRSS size={24} />
</a>
</div>
);
}Building custom icons
The library exports primitives so you can create your own animated icons with the same boop behavior:
import { createIcon, useAnimatedIcon, BaseSvg } from '@sudoh/animated-icons';
import type { AnimatedIconProps } from '@sudoh/animated-icons';
import { animated } from 'react-spring';
function IconHeart({ size, isBooped = false }: AnimatedIconProps) {
const [heartSpring] = useAnimatedIcon(isBooped, [
{
from: { transform: 'scale(1)' },
to: { transform: 'scale(1.2)' },
springiness: { tension: 300, friction: 10 },
},
]);
return (
<BaseSvg size={size} style={heartSpring}>
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z" />
</BaseSvg>
);
}
export default createIcon(IconHeart, 'Heart');How it works
useAnimatedIcon(isBooped, springs)— takes a boolean trigger and an array of spring definitions. Returns an array of animated style objects, one per spring.BaseSvg— a pre-configuredanimated.svgwith sensible defaults (24×24 viewBox, round stroke caps).createIcon(Component, altText)— wraps your icon component withIconWrapper, which handles hover/click detection and the boop timing.
Each spring definition looks like:
{
from: { transform: 'rotate(0deg)' }, // resting state
to: { transform: 'rotate(-28deg)' }, // booped state
springiness: { tension: 300, friction: 12 }, // optional spring config
delay: 50, // optional stagger delay in ms
}Accessibility
- All icons automatically respect
prefers-reduced-motion— animations are disabled when the user has requested reduced motion. - Icons are wrapped with alt text passed to
createIconfor screen reader context. - Every component is marked
'use client'for Next.js App Router compatibility.
License
MIT
