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.

Best Practices

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.

Try It — SVG Sprite Generator

Open full tool