How to Animate SVG Icons with CSS: Hover Effects, Transitions & Micro-Interactions
Learn how to animate SVG icons using pure CSS. Add hover transitions, spin loaders, pulse effects, stroke drawing animations, and micro-interactions — with accessible, production-ready code examples.
Why Animate SVG Icons?
Static icons communicate meaning. Animated icons communicate feedback. A button icon that scales on hover tells the user "this is interactive." A loading spinner shows that something is happening. A checkmark that draws itself confirms success.
These small motions — called micro-interactions — make interfaces feel responsive without adding complexity. And because SVG icons are DOM elements, you can animate them with the same CSS you already use for other HTML elements. No JavaScript libraries, no GIF files, no extra dependencies.
Here are the most useful animation patterns for SVG icons, starting from the simplest.
CSS Transitions: Hover and Focus States
The most common icon animation is a subtle response to user interaction. CSS transition handles this with a single line:
.icon {
transition: transform 0.2s ease;
}
.icon:hover {
transform: scale(1.15);
}
<button class="icon-button">
<svg class="icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5
2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09
C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5
c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
</button>
The icon smoothly scales up 15% on hover and returns to its original size on mouse-out. The ease timing function makes the motion feel natural.
Useful Hover Transitions
| Effect | CSS Property | Example Value |
|---|---|---|
| Scale up | transform: scale() | scale(1.15) |
| Rotate slightly | transform: rotate() | rotate(12deg) |
| Color change | fill or color | color: #0066cc |
| Lift (with shadow) | transform + filter | translateY(-2px) + drop-shadow() |
You can combine them:
.icon {
transition: transform 0.2s ease, color 0.2s ease, filter 0.2s ease;
color: #666;
}
.icon:hover {
transform: scale(1.1) rotate(5deg);
color: #0066cc;
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.15));
}
Focus States for Keyboard Users
Always add matching focus styles so keyboard users see the same feedback:
.icon-button:focus-visible .icon {
transform: scale(1.15);
color: #0066cc;
}
Using :focus-visible instead of :focus ensures the style only appears during keyboard navigation, not on mouse clicks.
CSS @keyframes: Continuous Animations
For animations that run continuously — loading spinners, pulsing indicators, attention-grabbing effects — use @keyframes:
Spin (Loading Indicator)
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.icon-spin {
animation: spin 1s linear infinite;
}
<svg class="icon-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"/>
</svg>
Use linear timing for smooth, continuous rotation. A partial circle (arc path) gives the classic spinner look.
Pulse (Notification Indicator)
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
}
.icon-pulse {
animation: pulse 2s ease-in-out infinite;
}
Good for notification dots, alert icons, or drawing attention to a new feature.
Bounce (Call to Action)
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
.icon-bounce {
animation: bounce 0.6s ease-in-out infinite;
}
Effective for download arrows, scroll indicators, or any icon that invites user action. Keep the distance small (3-5px) — subtle movement is more professional than large jumps.
Stroke Animation: The Drawing Effect
SVG's stroke-dasharray and stroke-dashoffset properties enable a unique effect: making an icon appear to draw itself. This is only possible with SVG and is one of the strongest reasons to use SVG icons over raster formats.
How It Works
The technique uses a dashed stroke where the dash length equals the total path length. By animating the offset, the visible portion of the stroke moves from nothing to the full path:
.icon-draw path {
stroke-dasharray: 100;
stroke-dashoffset: 100;
animation: draw 1s ease forwards;
}
@keyframes draw {
to { stroke-dashoffset: 0; }
}
<svg class="icon-draw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
This draws a checkmark from start to end — a satisfying confirmation animation.
Finding the Right dasharray Value
The stroke-dasharray value should match the path's total length. You can calculate it with JavaScript:
const path = document.querySelector('svg path');
console.log(path.getTotalLength()); // e.g., 84.52
Then use that value for both stroke-dasharray and the initial stroke-dashoffset. For simple icons, estimating works fine — try values between 50 and 200.
Trigger on Scroll or State Change
Instead of playing immediately, trigger the drawing effect when the element enters the viewport or when a state changes:
.icon-draw path {
stroke-dasharray: 100;
stroke-dashoffset: 100;
transition: stroke-dashoffset 0.8s ease;
}
.icon-draw.visible path {
stroke-dashoffset: 0;
}
Add the .visible class via JavaScript when the element scrolls into view or when a success state is reached.
Transform-Origin: Getting Rotation Right
When rotating or scaling SVG icons, the transform origin matters. By default, CSS transforms originate from the center of the element's box (50% 50%), which works for most SVG icons. But if your icon isn't centered within its viewBox, the animation may wobble.
Fix this explicitly:
.icon {
transform-origin: center;
/* or for precise control: */
transform-origin: 12px 12px; /* center of a 24x24 viewBox */
}
For icons that should rotate around a specific point (like a gear rotating around its axle), adjust the origin to that point:
.gear-icon {
transform-origin: 12px 12px;
transition: transform 0.3s ease;
}
.gear-icon:hover {
transform: rotate(90deg);
}
Performance: What to Animate (and What to Avoid)
Not all CSS properties animate equally. For smooth 60fps animations:
Animate these (GPU-accelerated):
transform(scale, rotate, translate)opacity
Avoid animating these (cause repaint):
fillorstrokecolor (works, but causes CPU-bound repaints)width,height(triggers layout recalculation)d(path data — not animatable in CSS; use SMIL or JavaScript)
For color transitions on hover, the performance impact is negligible on modern hardware. But for continuous animations, stick to transform and opacity.
/* Good — GPU-accelerated */
.icon {
transition: transform 0.2s ease, opacity 0.2s ease;
}
/* Avoid for continuous animations */
.icon-bad {
animation: colorCycle 2s infinite; /* repaints every frame */
}
Accessibility: Respecting prefers-reduced-motion
Some users experience motion sickness, vestibular disorders, or simply prefer reduced motion. The prefers-reduced-motion media query lets you disable or reduce animations for these users:
@media (prefers-reduced-motion: reduce) {
.icon,
.icon-spin,
.icon-pulse,
.icon-bounce {
animation: none;
transition: none;
}
}
This is not optional — it's a core accessibility requirement. Every animated icon should respect this preference.
A more nuanced approach keeps hover feedback but removes continuous motion:
@media (prefers-reduced-motion: reduce) {
/* Stop continuous animations */
.icon-spin,
.icon-pulse,
.icon-bounce {
animation: none;
}
/* Keep hover feedback, but make it instant */
.icon {
transition-duration: 0.01s;
}
}
Animating AI-Generated SVG Icons
Icons from AI tools (including our AI Icon Generator) arrive as static SVGs. Here's the workflow to add animations:
Step 1: Generate your icon and optimize it with the SVG Optimizer
Step 2: Set up currentColor for flexible styling (see our color guide)
Step 3: Add a CSS class and define your animation
<!-- AI-generated icon with hover animation -->
<button class="action-button" aria-label="Add to favorites">
<svg class="icon" aria-hidden="true" viewBox="0 0 24 24" fill="currentColor">
<!-- AI-generated path data -->
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5..."/>
</svg>
</button>
.action-button .icon {
transition: transform 0.2s ease;
}
.action-button:hover .icon {
transform: scale(1.15);
}
.action-button:focus-visible .icon {
transform: scale(1.15);
}
@media (prefers-reduced-motion: reduce) {
.action-button .icon {
transition: none;
}
}
This builds on the accessibility patterns from our previous post — the SVG is hidden from screen readers with aria-hidden, and the button carries the accessible label.
Quick Reference
| Animation | CSS Technique | Best For |
|---|---|---|
| Scale on hover | transition + transform: scale() | Buttons, interactive icons |
| Color on hover | transition + color | Navigation, links |
| Continuous spin | @keyframes + rotate | Loading indicators |
| Pulse | @keyframes + scale + opacity | Notifications, alerts |
| Bounce | @keyframes + translateY | CTAs, scroll indicators |
| Stroke drawing | stroke-dashoffset animation | Success confirmations, onboarding |
Timing guidelines:
- Hover/focus transitions: 150-250ms
- State change animations: 300-500ms
- Loading spinners: 800-1200ms per cycle
- Always add
prefers-reduced-motionhandling
Related Tools
- AI Icon Generator — Generate SVG icons, then bring them to life with these animation techniques
- SVG Viewer — Preview your SVG structure and test animations in real time
- SVG Optimizer — Clean up AI-generated SVGs for optimal animation performance