TL;DR
- Eight copy-paste CSS icon hover effects: spring scale, tilt, bell ring, wiggle, bounce, spin, stroke draw and colour sweep
- Animate only transform and opacity, trigger from the button, and pair :hover with :focus-visible
- transform-origin and an overshooting cubic-bezier make simple effects feel springy
- For icons that change shape or state, see the animated icons collection
A good icon hover animation is small, fast and a little bit playful. This guide gives you eight CSS icon hover effects you can copy into any project, plain HTML or React, plus the rules that keep them smooth and accessible.
Every example assumes an icon inside a button:
<button class="icon-btn" aria-label="Notifications">
<svg class="icon" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">…</svg>
</button>The base rules
.icon-btn .icon {
transition: transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: center;
}
@media (prefers-reduced-motion: reduce) {
.icon-btn .icon,
.icon-btn .icon * { animation: none !important; transition: none !important; }
}transform and opacity. They run on the compositor, so they stay at 60fps..icon-btn:hover .icon. The hit area is bigger and keyboard focus can share the selector.:hover with :focus-visible.1. Spring scale
.icon-btn:hover .icon,
.icon-btn:focus-visible .icon { transform: scale(1.18); }The overshooting curve in the base rule makes it pop rather than grow.
2. Tilt
.icon-btn:hover .icon,
.icon-btn:focus-visible .icon { transform: rotate(-12deg) scale(1.08); }Good for pencils, search icons and arrows.
3. Bell ring
@keyframes ring {
0%, 100% { transform: rotate(0); }
20% { transform: rotate(14deg); }
40% { transform: rotate(-12deg); }
60% { transform: rotate(8deg); }
80% { transform: rotate(-4deg); }
}
.icon-btn.bell .icon { transform-origin: 50% 10%; }
.icon-btn.bell:hover .icon,
.icon-btn.bell:focus-visible .icon { animation: ring 600ms ease-in-out; }Moving transform-origin to the top makes the bell swing from its hook. For a bell that also toggles muted and unmuted with a slash drawing in, see the Mute Bell Toggle.
4. Wiggle
@keyframes wiggle {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-2px) rotate(-4deg); }
75% { transform: translateX(2px) rotate(4deg); }
}
.icon-btn:hover .icon { animation: wiggle 300ms ease-in-out 2; }5. Bounce up
@keyframes bounce-up {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-5px); }
70% { transform: translateY(1px); }
}
.icon-btn:hover .icon { animation: bounce-up 450ms cubic-bezier(0.34, 1.56, 0.64, 1); }6. Spin
.icon-btn.refresh .icon { transition: transform 600ms cubic-bezier(0.65, 0, 0.35, 1); }
.icon-btn.refresh:hover .icon { transform: rotate(360deg); }7. Stroke draw-on
For line icons (Lucide, Feather, Heroicons outline), redraw the stroke on hover:
.icon-btn .icon path {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
.icon-btn:hover .icon path { animation: draw 500ms ease-out; }
@keyframes draw {
from { stroke-dashoffset: 1; }
to { stroke-dashoffset: 0; }
}Add pathLength="1" to each <path> so 1 means the full length of every path. More SVG techniques are in animated SVG icons.
8. Colour fill sweep
.icon-btn .icon { color: #8a8a8a; transition: color 160ms ease, transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1); }
.icon-btn:hover .icon { color: #ff5a36; transform: scale(1.08); }Icons that use stroke="currentColor" or fill="currentColor" follow the button's color, so one property recolours the whole icon.
Doing it in React
With React the CSS stays the same; wrap it in a component and pass the effect as a class:
export function IconButton({ label, effect = 'scale', children }: {
label: string
effect?: 'scale' | 'tilt' | 'bell' | 'refresh'
children: React.ReactNode
}) {
return (
<button aria-label={label} className={`icon-btn ${effect}`}>
{children}
</button>
)
}To animate Lucide icons specifically, see how to animate Lucide icons in React.
When CSS is not enough
CSS hover effects cannot change an icon's shape, hold multiple states, or react to where the pointer is. For hover icons with real squash-and-stretch, nav icons with a different move each, and toggles, use an interactive format such as Rive. The animated icons collection has those ready to paste into Cursor, Claude Code, Lovable or v0.
More articles
Animated Icons in React: 5 Ways to Add Them (CSS, SVG, Motion, Lottie, Rive)
How to add animated icons to a React or Next.js app: CSS hover transitions, animated SVG, Motion, Lottie and Rive, with code for each and a guide to picking the right one.
Animated SVG Icons: How to Make Icons Move (Draw, Morph, Transform)
How to make animated SVG icons: stroke drawing with pathLength, transforms with transform-box, path morphing, hover triggers and React components, plus the mistakes that break them.
Lottie vs Rive vs CSS for Animated Icons
Lottie, Rive or CSS for animated icons? A side-by-side comparison of file size, interactivity, editing workflow and accessibility, and which to pick for hover icons, toggles and reactions.