Most web animations don’t need a JavaScript library. Learning how to animate SVG with CSS gets you stroke draw effects, looping spinners, icon transitions, and hover states with zero runtime cost.
SVG elements are part of the DOM, which means CSS targets them directly using the same selectors, @keyframes, and transition syntax you already know.
This guide covers every technique worth knowing: CSS transitions and keyframe animations on SVG elements, the stroke-dashoffset line draw method, transform-origin fixes, embedding methods, performance, and when to reach for GSAP or Anime.js instead.
No fluff. Just the practical mechanics, in order.
What Is CSS SVG Animation?
CSS SVG animation is the application of CSS transitions, keyframe animations, and transform properties directly to SVG elements using class or ID selectors.
Because SVG elements are part of the DOM, CSS animation properties apply to them the same way they apply to standard HTML elements — with some attribute-specific exceptions covered below.
| Method | Browser Support | File Size Impact | JS Required |
|---|---|---|---|
| CSS animations | All modern browsers | None | No |
| SMIL | Partial (uncertain future) | None | No |
| JavaScript (GSAP, Anime.js) | All modern browsers | 14–30 KB added | Yes |
SVG files save up to 60% of network bandwidth compared to PNG, and animated SVG spinners weigh under 2 KB each — compared to 50–200 KB for a typical GIF spinner (Colorlib, 2024).
How CSS Targets SVG Elements
CSS targets SVG elements through standard selectors. Any SVG element with a class, ID, or tag name is reachable from an external stylesheet or a <style> block inside the SVG.
This only works when the SVG is inline in HTML. External SVGs loaded via <img> tags are isolated from the parent page’s CSS entirely.
Targeting methods:
- Class selector:
.icon-path { fill: blue; } - ID selector:
#logo-circle { opacity: 0.5; } - Tag selector:
circle { stroke: red; } - Descendant selector:
.svg-wrapper path { stroke-dashoffset: 0; }
What CSS Can and Cannot Animate in SVG
CSS animates SVG presentation attributes — fill, stroke, opacity, stroke-width, fill-opacity — natively and reliably.
CSS handles these well:
- fill, stroke, stroke-width, stroke-opacity
- opacity and fill-opacity
- transform (translate, rotate, scale, skew)
- filter, clip-path, mask
CSS cannot animate these without workarounds:
- The
dattribute (SVG path shape) cx,cy,ron circle elementsx,y,width,heighton rect elements
Geometric attributes like d and cx require JavaScript or CSS Houdini. Path morphing specifically needs GSAP’s MorphSVGPlugin or Flubber.js.
What CSS Properties Work on SVG Elements?
CSS animation on SVG elements works through 2 property categories: presentation attributes (fill, stroke, opacity) and transform functions (translate, rotate, scale). Each category has distinct browser behavior and performance characteristics.
Animatable presentation attributes:
- fill — color of the shape interior
- stroke — color of the shape outline
- stroke-width — thickness of the outline
- opacity / fill-opacity / stroke-opacity — transparency controls
- color — inherited color value
Transform properties behave differently on SVG than on HTML. The SVG coordinate system is defined by viewBox, not the viewport, so transform-origin: 50% 50% centers on the SVG canvas — not on the element itself. Adding transform-box: fill-box fixes this in Chrome 64+ and Firefox 55+.
SVG usage in image requests grew 36% between 2022 and 2024, now accounting for 6.4% of all web image requests (HTTP Archive, 2024 Media chapter).
CSS Custom Properties Inside SVG
CSS custom properties (variables) work inside SVG elements. Declare them on :root or directly on the SVG element, then reference them with var() in animation values.
:root {
--draw-duration: 1.2s;
--icon-color: #3a86ff;
}
.svg-path {
stroke: var(--icon-color);
animation-duration: var(--draw-duration);
}
Practical use cases:
- Theming animated icons across a design system
- Controlling animation speed from a parent component in React or Vue
- Updating animation values with JavaScript without rewriting keyframes
CSS variables penetrate the SVG shadow DOM when using the <use> element, unlike direct property animations. This makes them the preferred way to pass values into reusable SVG components.
How Do CSS Transitions Animate SVG?
CSS transitions animate SVG elements between 2 states by interpolating property values over a set duration. They require a trigger — typically :hover, :focus, a class toggle, or a :checked state.
The transition shorthand on an SVG element works identically to HTML: transition: fill 0.3s ease, opacity 0.5s linear.
SVG Line Draw Effect with stroke-dashoffset
The stroke-dashoffset technique is the most widely used CSS-only SVG effect. It simulates a path drawing itself by animating the offset of a dashed stroke pattern.
How it works — 3 steps:
- Set
stroke-dasharrayequal to the total path length - Set
stroke-dashoffsetto that same value (hides the stroke) - Animate
stroke-dashoffsetto0(reveals the stroke progressively)
.draw-path {
stroke-dasharray: 340;
stroke-dashoffset: 340;
transition: stroke-dashoffset 1s ease;
}
.draw-path:hover {
stroke-dashoffset: 0;
}
Get the exact path length in JavaScript with pathElement.getTotalLength(), then hardcode that value in the CSS.
Performance note: Animating stroke-dashoffset triggers paint on each frame. It runs slower than opacity or transform animations, which run on the compositor thread. For complex illustrations with many paths, consider GPU promotion with will-change: transform on the SVG container.
Hover State Transitions on SVG Icons
State-based transitions on SVG icons cover the majority of real interactive elements built with CSS alone.
Common patterns:
fillcolor change on:hover(social icons, nav icons)opacityfade on:focus(accessible keyboard interactions)transform: scale()on:active(button press feedback)
Transitions on fill trigger repaint. Transitions on opacity and transform run on the compositor and stay at 60 frames per second without layout recalculation (MDN Web Docs).
Tokopedia replaced JavaScript scroll event listeners with CSS-based transitions and reduced their animation code by up to 80% while keeping frame rates consistent (Chrome for Developers, 2023).
How Do CSS Keyframe Animations Work on SVG?
@keyframes animations apply multi-step motion sequences to SVG elements using the same syntax as HTML element animation. The animation shorthand controls timing, looping, and state after the animation ends.
@keyframes pulse {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.6; transform: scale(0.95); }
100% { opacity: 1; transform: scale(1); }
}
.svg-icon {
animation: pulse 2s ease-in-out infinite;
}
| Property | Controls | Common Values |
|---|---|---|
| animation-duration | Total cycle length | 0.3s, 1s, 2s |
| animation-timing-function | Speed curve | ease, linear, cubic-bezier() |
| animation-iteration-count | Repeat behavior | 1, infinite |
| animation-fill-mode | State after end | forwards, both |
| animation-delay | Start offset | 0s, 0.2s, 0.5s |
Animating SVG Spinners and Loaders
SVG CSS loaders built with @keyframes are the most performance-efficient loading indicators available. They weigh under 2 KB, scale to any resolution, and match brand colors with a single variable change.
3 common spinner patterns:
- Rotating circle —
transform: rotate(360deg)withanimation-iteration-count: infinite - Pulsing dots —
transform: scale()keyframes with staggeredanimation-delayvalues - Stroke draw loop —
stroke-dashoffsetcycling from full to zero, then resetting
The key detail: always pair animation-iteration-count: infinite with animation-timing-function: linear on rotation animations. Any easing function creates a visible speed bump at each loop reset.
Sequencing Multiple SVG Elements with animation-delay
Stagger effects on multi-path SVG illustrations use animation-delay offsets across sibling elements. Each path gets the same @keyframes block but starts at a different time.
Structure:
.path-1 { animation: draw 1s ease forwards; }
.path-2 { animation: draw 1s ease forwards 0.2s; }
.path-3 { animation: draw 1s ease forwards 0.4s; }
Set animation-fill-mode: backwards on delayed elements so they start hidden before their animation begins. Without it, the element renders at its 0% keyframe state until the delay expires — which causes a visible flash on page load.
How Does the SVG Coordinate System Affect CSS Transforms?
SVG uses an internal coordinate system defined by the viewBox attribute, not by the browser viewport. CSS transforms on SVG elements use this internal coordinate system as their reference, which causes transform-origin to behave differently than on HTML elements.
The core problem: transform-origin: 50% 50% on an SVG element calculates 50% relative to the entire SVG canvas — not the element’s own bounding box. A circle at the top-left of a 500×500 SVG will appear to orbit the canvas center instead of rotating in place.
The fix — 2 properties together:
.svg-icon {
transform-box: fill-box;
transform-origin: center;
}
transform-box: fill-box tells the browser to use the element’s own bounding box (not the SVG viewport) as the reference for transform-origin. This is supported in Chrome 64+, Firefox 55+, and Safari 11.1+.
SVG 1.1 Transform Attribute vs. CSS Transform
SVG elements accept a transform attribute directly in the markup: <g transform="rotate(45)">. This attribute and the CSS transform property coexist but do not stack cleanly.
What happens when both are present:
- Older browsers apply the SVG attribute transform first, then the CSS transform on top
- This produces unexpected compound transformations
- The fix: remove the SVG
transformattribute from the markup and handle all transforms in CSS
Tools like SVGO (used in SVG optimization workflows) can strip inline transform attributes during export, keeping the markup clean for CSS-only animation.
How to Animate SVG Paths with CSS?
SVG path animation with CSS centers on 2 techniques: the stroke-dashoffset draw effect for line reveals, and opacity or transform animations for path visibility changes. Path morphing (changing the actual shape) requires JavaScript.
The stroke-dashoffset technique — popularized by Polygon’s Xbox One review in 2013 — remains one of the most visually effective CSS-only SVG effects available (Colorlib, 2024).
The stroke-dashoffset Line Draw Technique
Full implementation:
@keyframes draw {
from { stroke-dashoffset: 480; }
to { stroke-dashoffset: 0; }
}
.svg-path {
stroke-dasharray: 480;
stroke-dashoffset: 480;
animation: draw 1.5s ease forwards;
}
The value 480 must match the actual path length. Wrong values either cut the animation short or leave a gap at the end.
3 ways to get the correct value:
pathElement.getTotalLength()in the browser console- SVG editing software path length readout (Illustrator, Figma)
- Estimated trial-and-error for simple shapes (circles, rectangles)
Path Length Calculation with getTotalLength()
getTotalLength() is a built-in SVG DOM method that returns the total length of a path element in user units.
const path = document.querySelector('.my-path');
console.log(path.getTotalLength()); // e.g., 342.7
Run this in DevTools on the live page, then hardcode the returned value into your CSS. The value stays constant as long as the path data does not change.
Important: getTotalLength() is not available on SVG shapes like <circle>, <rect>, or <polygon>. Convert them to <path> elements first if you need this measurement.
Path morphing — animating the d attribute between 2 different shapes — is outside CSS capability. GSAP’s MorphSVGPlugin handles this and allows morphing between paths with different node counts, which SVG itself cannot do natively.
How to Control SVG Animation with CSS Custom Properties?
CSS custom properties inside SVG create reusable animation systems that can be updated from a parent element, a JavaScript toggle, or a design token. They reduce duplication when the same animation appears across multiple components.
Basic setup:
:root {
--anim-duration: 0.8s;
--anim-delay: 0s;
--brand-stroke: #4f46e5;
}
.animated-icon {
stroke: var(--brand-stroke);
animation-duration: var(--anim-duration);
animation-delay: var(--anim-delay);
}
Changing --anim-duration on :root updates every icon using that variable instantly — no keyframe rewrites needed.
Passing Custom Properties into Components
In React or Vue component systems, custom properties set on a wrapper element cascade into inline SVG children:
<div style={{ '--anim-duration': '1.2s', '--brand-stroke': '#ef4444' }}>
<AnimatedIcon />
</div>
This pattern lets each instance of a component carry its own animation timing without prop drilling or className variants. It also works through the <use> element’s shadow DOM boundary, which direct CSS property animations cannot cross.
Updating with JavaScript at runtime:
document.documentElement.style.setProperty('--anim-duration', '0.3s');
This triggers re-animation across all elements using that variable immediately. Useful for reduced-motion toggles, speed controls, or interactive playback systems without touching keyframe definitions.
How Does SVG Animation Performance Work?
Smooth SVG animation runs at 60 frames per second, which gives the browser exactly 16.7 milliseconds per frame to execute scripts, recalculate styles, and repaint (MDN Web Docs).
Not all CSS properties hit that target equally. The properties you choose to animate directly determine whether the browser stays on the compositor thread or falls back to expensive layout and paint operations.
| Property | Triggers Layout | Triggers Paint | Compositor Thread |
|---|---|---|---|
| opacity | No | No | Yes |
| transform | No | No | Yes |
| fill / stroke | No | Yes | No |
| stroke-width | No | Yes | No |
| filter | No | Yes | Partial (GPU) |
Which SVG Properties Trigger Layout vs. Paint
Safe to animate (compositor-thread only):
opacitytransform(translate, rotate, scale)
Triggers repaint — use with care:
fill,stroke,stroke-dashoffsetstroke-width,fill-opacity
Triggers layout — avoid in loops:
cx,cy,r,x,y,width,height(geometric attributes)
SVG <filter> elements like feGaussianBlur are GPU-accelerated in Chrome but expensive in Safari, where they run on the CPU. Test filter animations on Safari before shipping.
Using will-change on SVG Elements
will-change: transform tells the browser to promote an SVG element to its own compositor layer before the animation begins.
.animated-icon {
will-change: transform;
}
Use it selectively. Applying will-change to every SVG element on a page creates as many GPU layers as there are elements, which consumes memory and can slow down the page.
The correct pattern: add will-change only to elements that animate frequently or are about to animate, and remove it after the animation ends with JavaScript.
Chrome DevTools confirms layer promotion. Open the Rendering panel, enable “Layer Borders,” and look for a yellow outline around the promoted element.
How to Animate SVG Icons with CSS?
CSS handles the 3 most common SVG icon animation patterns — hamburger menus, animated checkmarks, and directional arrows — without any JavaScript dependency.
These are also the patterns most likely to affect perceived user experience. A toggle that snaps vs. one that transitions smoothly at 200ms feels like a completely different product.
Hamburger Menu to Close Icon Animation
The hamburger-to-X is the most-built CSS SVG animation on the web. 3 SVG <line> elements, class toggling, and CSS transitions on transform and opacity.
Structure:
.line-top { transform-origin: center; transition: transform 0.3s ease; }
.line-mid { transition: opacity 0.2s ease; }
.line-bottom { transform-origin: center; transition: transform 0.3s ease; }
.is-open .line-top { transform: rotate(45deg) translateY(8px); }
.is-open .line-mid { opacity: 0; }
.is-open .is-open .line-bottom { transform: rotate(-45deg) translateY(-8px); }
Add transform-box: fill-box on each line element to make rotation pivot around the line’s own center, not the SVG canvas center. Without it, the lines orbit an invisible point and the animation looks broken.
Toggle the .is-open class via JavaScript on the button click. The CSS handles everything else.
Accessible SVG Animation with prefers-reduced-motion
Vestibular disorders affect more than 70 million people worldwide, and on-screen animation is a documented trigger (CSS-Tricks). The prefers-reduced-motion media query detects the OS-level “reduce motion” preference and lets you respond at the CSS level.
@media (prefers-reduced-motion: reduce) {
.animated-icon,
.draw-path,
.svg-spinner {
animation: none;
transition: none;
}
}
WCAG 2.3.3 (AAA) requires a way to disable non-essential animations triggered by user interactions. Detecting reduced-motion in CSS is the most reliable implementation path (web.dev).
Alternatively — keep a subtle static state rather than cutting motion entirely:
@media (prefers-reduced-motion: reduce) {
.animated-icon {
animation-duration: 0.01ms;
animation-iteration-count: 1;
}
}
This stops the loop while letting the element reach its end state, which is often better than a sudden disappearance.
How to Embed SVG for CSS Animation to Work?
Inline SVG is the only embedding method that gives parent-page CSS full access to SVG internals.
MDN Web Docs confirms this directly: inlining SVG is the only approach that lets you use CSS interactions like :focus and CSS keyframes on SVG elements from an external stylesheet.
| Embedding Method | CSS Animation from Parent | JS Access | Browser Caching |
|---|---|---|---|
Inline <svg> | Full access | Full access | No |
<img src=".svg"> | None | None | Yes |
CSS background-image | None | None | Yes |
<object> tag | Limited (same-origin) | Limited | Yes |
Inline SVG vs. External SVG File for Animation
Choose inline SVG when:
- Icons need hover transitions or keyframe animations
- Fill or stroke colors change based on state or theme
- JavaScript needs to reach individual path elements
- The SVG is used once on the page (no caching benefit anyway)
Choose <img> tag when:
- The SVG is static (no animation needed)
- The same file appears on many pages (caching saves bandwidth)
- Markup cleanliness matters more than styling control
SVG markup under 5 KB is fine to inline. Above 5 KB, the <img> tag with lazy loading is often the better call for performance (SVG Genie, 2026).
Using the SVG <use> Element with CSS Animation
The <use> element clones SVG symbols into the DOM. CSS custom properties (variables) cross the shadow DOM boundary that <use> creates. Direct property animations do not.
<svg style="display: none">
<symbol id="icon-arrow" viewBox="0 0 24 24">
<path class="arrow-path" d="..." />
</symbol>
</svg>
<svg><use href="#icon-arrow" /></svg>
Set --stroke-color on the <svg> wrapper and reference it inside the symbol definition. The color update crosses the shadow boundary. Trying to select .arrow-path directly from the parent stylesheet does not work.
What Are Common CSS SVG Animation Mistakes?
Most broken SVG animations come down to 5 issues. Three of them are transform-related. Knowing these upfront saves hours of debugging.
The 5 most common mistakes:
- Missing transform-box — rotation and scale pivot on the SVG canvas center instead of the element. Fix: add
transform-box: fill-box; transform-origin: centerto every animated element. - Wrong stroke-dasharray value — the draw animation cuts short or leaves a visible gap. Fix: measure with
getTotalLength()and hardcode the exact value. - External SVG embed for animations — CSS from the parent page cannot reach
<img>SVG internals. Fix: switch to inline SVG. - Animating geometric attributes in CSS —
cx,r,d,x,yare not CSS properties. CSS ignores them silently. Fix: usetransform: translate()to move shapes instead of changing coordinates directly. - No animation-fill-mode on delayed elements — elements flash at their
0%keyframe state before theiranimation-delayexpires. Fix: addanimation-fill-mode: backwards.
The transform-box issue is the most common. It trips up experienced developers because it only shows up when you rotate or scale an element in place, and the breakage is not obvious until you actually test it.
How Do CSS SVG Animations Compare to JavaScript SVG Libraries?
CSS handles opacity, transform, fill, stroke transitions, and stroke draw effects with zero JavaScript and no added page weight.
GSAP is used on over 11 million websites and is the industry standard for complex, sequenced, and scroll-triggered SVG animation (DEV Community, 2025).
| Capability | CSS | GSAP | Anime.js |
|---|---|---|---|
| Fill / stroke transition | Yes | Yes | Yes |
| Stroke draw effect | Yes | Yes | Yes |
| Path morphing (d attribute) | No | Yes (MorphSVG plugin) | No |
| Timeline sequencing | No | Yes | Yes |
| Scroll-triggered animation | Partial (CSS 2024+) | Yes (ScrollTrigger) | No |
| File size added | 0 KB | ~25 KB (core) | ~17 KB |
When CSS Is Enough
CSS covers the full range of micro-interactions and UI-state animations without a runtime dependency.
Use CSS for:
- Hover transitions on icons and buttons
- Looping spinners and loaders
- Stroke draw reveals on page load
- State-based class toggles (open/closed, active/inactive)
These run at 60fps on the compositor thread with no JavaScript overhead. CSS animation files stay small because no library has to be parsed or executed.
When to Use GSAP or Anime.js
Pure CSS hits a hard wall with path morphing, timeline sequencing, and fine-grained playback control.
GSAP is the right call for:
- Morphing between 2 SVG shapes (MorphSVGPlugin handles unequal node counts)
- Scroll-linked animation progress with ScrollTrigger
- Sequenced multi-element animations where timing depends on other timings
- Pausing, reversing, and seeking animations programmatically
Anime.js (version 4, released 2024) reaches GSAP-level concurrent performance and comes in at approximately 17 KB minified and gzipped — lighter than GSAP core (ICS Media, 2026). It handles cx, cy, r, and other SVG DOM attributes that CSS cannot animate, making it a practical middle option for projects that need attribute animation without the full GSAP stack.
Webflow made GSAP 100% free in 2024, removing the licensing cost that previously pushed smaller projects toward Anime.js.
FAQ on How To Animate SVG With CSS
Can you animate SVG with CSS only?
Yes. CSS handles fill, stroke, opacity, and transform animations on SVG elements with no JavaScript required. For path morphing or timeline sequencing, you need GSAP or Anime.js. Most icon and UI animations work fine with pure CSS.
Why is my SVG transform not working correctly?
Missing transform-box: fill-box. Without it, transform-origin calculates relative to the SVG canvas, not the element. Add transform-box: fill-box; transform-origin: center to every element you rotate or scale.
What is the stroke-dashoffset trick?
It is the standard CSS technique for SVG path draw animations. Set stroke-dasharray equal to the path length, then animate stroke-dashoffset from that value to zero. The stroke appears to draw itself.
How do I get the SVG path length for the animation?
Run document.querySelector('path').getTotalLength() in the browser console on the live page. Copy the returned value and hardcode it as your stroke-dasharray and starting stroke-dashoffset value in CSS.
Does CSS animation work on externally embedded SVGs?
No. SVG loaded via <img> or CSS background-image is isolated from the parent page stylesheet. Inline SVG is the only embedding method that gives your CSS full access to SVG elements and keyframe animations.
How do I animate an SVG icon on hover?
Target the SVG element or its children with a CSS :hover selector and use the transition property. For example: transition: fill 0.3s ease. Add transform-box: fill-box if you are also applying scale or rotation transforms.
What CSS properties can I animate on SVG elements?
You can animate fill, stroke, stroke-width, opacity, fill-opacity, stroke-opacity, and all transform functions. You cannot animate geometric attributes like cx, r, or the path d attribute with CSS alone.
How do I make SVG animation accessible?
Use the prefers-reduced-motion media query to disable or reduce animations for users who have enabled reduced motion in their OS settings. Set animation: none and transition: none inside that media query block.
Why does my SVG animation cause jank?
Animating fill or stroke triggers browser repaint on every frame. Switch to animating opacity and transform instead — both run on the compositor thread and stay smooth at 60fps without layout or paint recalculation.
When should I use GSAP instead of CSS for SVG animation?
Use GSAP when you need path morphing, scroll-linked animation progress, or sequenced multi-element timelines. For standard icon transitions, looping CSS spinners, and stroke draw effects, plain CSS is faster to write and adds zero page weight.
Conclusion
Knowing how to animate SVG with CSS gives you a reliable, lightweight toolset that covers the majority of real-world animation needs without touching a JavaScript library.
Start with transform-box: fill-box on every element you rotate or scale. Get comfortable with the stroke-dashoffset line draw technique. Use opacity and transform for anything performance-sensitive.
Inline your SVG, keep animation-fill-mode in mind for delayed sequences, and always add a prefers-reduced-motion fallback for accessibility.
For path morphing or complex keyframe animation timelines, GSAP or Anime.js picks up where CSS stops.
Most projects need far less than they think. Build with CSS first, then add a library only when the use case actually demands it.
