Animating SVG Sprite Icons
Add CSS and SMIL animations to SVG sprite icons. Learn spinner animations, hover transitions, loading states, and which animation techniques work through <use> references.
Detailed Explanation
Animating SVG Sprite Icons
SVG sprite icons can be animated using CSS, but the shadow DOM boundary created by <use> adds some constraints. Here is what works and what does not.
CSS Animations on the Outer <svg>
The simplest approach is animating the entire <svg> element that contains the <use> reference:
/* Spinner animation */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.icon-spinner {
animation: spin 1s linear infinite;
}
<svg class="icon-spinner" width="24" height="24">
<use href="#icon-loader"/>
</svg>
This works because you are animating the container, not the internals.
Hover Transitions
Color transitions on hover work through currentColor:
.icon-link {
color: #666;
transition: color 0.2s ease;
}
.icon-link:hover {
color: #333;
}
<a href="/" class="icon-link">
<svg width="20" height="20">
<use href="#icon-home"/>
</svg>
</a>
Scale and transform transitions also work:
.icon-button svg {
transition: transform 0.15s ease;
}
.icon-button:hover svg {
transform: scale(1.1);
}
.icon-button:active svg {
transform: scale(0.95);
}
What Does NOT Work Through <use>
You cannot animate individual paths or groups inside a <use> reference from external CSS. The shadow DOM prevents CSS selectors from reaching inside:
/* This will NOT work */
#icon-menu line:nth-child(2) {
transform: rotate(45deg);
}
Workaround: Inline SVG for Complex Animations
For icons that need internal part animation (e.g., hamburger menu morphing into an X), inline the SVG instead of using a sprite reference:
<!-- Inline for complex animation -->
<svg class="menu-icon" viewBox="0 0 24 24">
<line class="top" x1="3" y1="6" x2="21" y2="6"/>
<line class="middle" x1="3" y1="12" x2="21" y2="12"/>
<line class="bottom" x1="3" y1="18" x2="21" y2="18"/>
</svg>
.menu-icon line {
transition: transform 0.3s ease, opacity 0.3s ease;
transform-origin: center;
}
.menu-icon.open .top { transform: rotate(45deg) translate(0, 6px); }
.menu-icon.open .middle { opacity: 0; }
.menu-icon.open .bottom { transform: rotate(-45deg) translate(0, -6px); }
SMIL Animations Inside Symbols
SMIL animations defined inside <symbol> elements DO carry through <use>:
<symbol id="icon-pulse" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="8" fill="currentColor">
<animate attributeName="opacity"
values="1;0.3;1" dur="1.5s"
repeatCount="indefinite"/>
</circle>
</symbol>
However, SMIL is deprecated in Chrome (though still supported) and may not be future-proof. CSS animations are preferred.
Loading State Pattern
A common pattern is switching between a static icon and an animated loader:
function SubmitButton({ loading }: { loading: boolean }) {
return (
<button disabled={loading}>
<svg width="20" height="20" className={loading ? "animate-spin" : ""}>
<use href={loading ? "#icon-loader" : "#icon-check"} />
</svg>
{loading ? "Saving..." : "Save"}
</button>
);
}
Using Tailwind's animate-spin utility is a clean way to add spinner behavior.
Use Case
Front-end developers adding micro-interactions to icon buttons, teams building loading states and transitions in component libraries, and UI developers creating polished hover effects for icon-based navigation.