An SVG is drawing instructions rather than a flat picture, which means every line, curve and fill inside it is a real element you can target and move. That is what makes SVG such a natural fit for animation. You are not nudging a static image around; you are animating the shapes themselves. A stroke can appear to draw, a group can rotate, a colour can shift, all while the icon stays perfectly sharp.
This guide covers the practical toolkit. We start with CSS, which handles the majority of what you will ever need, then move to the line-drawing effect that SVG is famous for, then to SMIL for animation that lives inside the file, and finally to when a JavaScript library is actually worth the weight. Throughout, the aim is motion that adds meaning rather than motion for its own sake. If you are new to the format, the explainer on what an SVG file is and the guide to using SVG icons are useful groundwork.
The short version
For most icons, inline the SVG and animate it with CSS: a transition for hover states, a @keyframes loop for spinners. Animate transform and opacity, not layout properties, so it stays smooth. Use stroke-dasharray for the draw-on effect, reach for SMIL only when the motion must travel inside the file, and always wrap it in a prefers-reduced-motion check.
Three ways to animate an SVG
There are three established approaches, and the right one depends mostly on where the SVG lives and how self-contained the animation needs to be.
| Approach | Best for | Note |
|---|---|---|
| CSS | Hover effects, spinners, page-triggered motion | Needs the SVG inline in the HTML so CSS can reach it |
SMIL (<animate>) | Motion that must travel with the file | Written inside the SVG; works even as an img |
| JavaScript library | Complex, scripted or timeline animation | Powerful but adds weight; overkill for simple icons |
The single most important requirement is this: your page CSS can only animate an SVG that is inline in the HTML. If the icon is loaded through an <img> tag or a CSS background, your stylesheet cannot see inside it, so CSS animation is off the table and you would need SMIL instead. That one rule decides a lot, so keep it in mind as you read on.
Animate with CSS
CSS is where you should start, because it uses skills you already have and covers the vast majority of icon animation. Inline the SVG, give the part you want to move a class, and animate it like any other element.
Hover with a transition. A transition smoothly interpolates a property when its value changes, which is perfect for hover states. Here a settings icon eases into a quarter turn when you point at it:
.gear { transition: transform .3s ease; transform-origin: center; }
.gear:hover { transform: rotate(90deg); }
Loop with keyframes. For continuous motion like a loading spinner, define a @keyframes sequence and run it forever:
@keyframes spin { to { transform: rotate(360deg); } }
.spinner { transform-origin: center; animation: spin .9s linear infinite; }
That is genuinely all a spinner needs. The same pattern, with different keyframes, gives you a pulsing dot, a bouncing arrow or a shaking bell.
The transform-origin trap
The most common CSS-on-SVG surprise is rotation happening around the wrong point. By default an SVG element rotates around the origin of its coordinate system, the top-left corner, not its own centre, so an icon can swing wildly off to one side. Fix it by setting transform-origin: center on the element, or by adding transform-box: fill-box so the origin is measured against the shape's own box. Set both and rotation behaves the way you expect.
The draw-on effect with stroke-dasharray
If there is one animation people associate with SVG, it is a line drawing itself onto the screen, the trick behind those satisfying animated checkmarks and signatures. It relies on two stroke properties working together, and it only works on stroked paths, not solid fills.
A dashed stroke is controlled by stroke-dasharray (the pattern of dashes and gaps) and stroke-dashoffset (how far the pattern is shifted along the line). The technique is to make a single dash exactly as long as the whole path, then offset it by that same length so the entire stroke sits in the gap and is invisible. Animating the offset back to zero slides the dash into view, so the line appears to draw:
.draw { stroke-dasharray: 100; stroke-dashoffset: 100; animation: draw 1s ease forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }
The one value you need is the path's length, used for both the dasharray and the starting offset. You can read it exactly in the browser with one line of JavaScript, path.getTotalLength(), then drop that number into your CSS. For a checkmark or a short icon path, a round number a little larger than the visible length is usually close enough to look right. Pair the draw with a fade or a slight scale and you have the confirmation animation seen all over modern interfaces.
Animation inside the file with SMIL
Sometimes you want the animation to belong to the icon itself, so it plays wherever the file goes, even when it is dropped in as an <img> or shared with someone who never sees your CSS. That is what SMIL is for. SMIL animation is written as elements inside the SVG, most often <animate> for a single attribute and <animateTransform> for movement, rotation or scaling.
A self-contained spinner looks like this, with the rotation defined right inside the SVG:
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor"
stroke-width="2.4" stroke-linecap="round" stroke-dasharray="42 14">
<animateTransform attributeName="transform" type="rotate"
from="0 12 12" to="360 12 12" dur="0.9s" repeatCount="indefinite"/>
</circle>
Notice the rotation centre is written as part of the value, 12 12, so SMIL sidesteps the transform-origin trap that catches CSS. The animated icons in the ICON OOP tool are built exactly this way, which is why they keep moving when you save one and reuse it elsewhere: the motion is baked into the file rather than added by a stylesheet.
You may have heard SMIL was being removed from browsers. That was proposed for Chrome years ago and then reversed, so it is supported across current browsers and safe to use where self-contained animation is genuinely what you need. For animation the page controls, such as a hover or a spinner that only appears while loading, CSS is still the simpler tool.
When to reach for a JavaScript library
CSS and SMIL cover almost everything an icon needs. A JavaScript animation library earns its place only when you step beyond simple, self-contained motion:
- Scripted timelines and sequencing. When several elements must animate in a precisely choreographed order with fine timing control, a library such as GSAP makes that far easier than hand-written keyframes.
- Designer-authored animation. Complex illustrated animations exported from design tools are often delivered as Lottie files and played with a small runtime, which is a different job from animating a UI icon.
- Interaction-driven motion. Animation that must respond continuously to scrolling, dragging or live data usually needs JavaScript to drive it frame by frame.
For a spinner, a hover wiggle or a checkmark, a library is weight you do not need. Reach for one when the animation itself is the feature, not when you are decorating a button. Keeping simple icons on plain CSS also keeps your pages light, which matters more than a fancier animation almost every time.
Micro-interactions worth adding
Good icon animation is quiet. It confirms an action or draws the eye for a moment, then gets out of the way. A few that reliably improve an interface:
- A spinner while something loads. The clearest possible signal that the app is working and has not frozen.
- A checkmark that draws on success. A brief, positive confirmation that an action completed, using the stroke-draw effect above.
- A gentle hover shift on interactive icons. A small rotation, scale or colour change tells people an icon is clickable before they click it.
- A single bell shake for a new notification. One short movement catches attention without nagging. Repeating it endlessly does the opposite.
- A smooth toggle between two states. A menu icon easing into a close icon, or a chevron rotating open, makes state changes feel intentional.
The common thread is restraint. Motion that plays once, in response to something the user did, reads as polish. Motion that loops forever for no reason reads as noise and, worse, as a distraction people cannot switch off.
Respect reduced motion
Animation is not free for everyone. For some people, motion on screen can trigger dizziness, nausea or migraine, which is why every major operating system has a setting to reduce motion, and why browsers expose that choice to your CSS. Honouring it is a baseline accessibility expectation, and it is only a few lines.
The prefers-reduced-motion media query lets you scale animation back for anyone who has asked for it, while keeping it for everyone else:
@media (prefers-reduced-motion: reduce) {
.spinner, .gear, .draw { animation: none; transition: none; }
}
You do not have to remove everything. Often the right move is to keep essential feedback, a spinner still needs to indicate loading, while cutting decorative flourishes and shortening durations. The goal is to respect the request without stripping out information the user relies on. This sits alongside the wider practice in the accessible icons guide, which covers labelling and screen readers; motion preferences are the same idea applied to movement.
Animate the cheap properties
Smooth animation comes down to what you animate, not just how much. Browsers can move and fade elements very cheaply, but changing properties that affect layout forces expensive recalculation on every frame, which is what causes janky, stuttering motion.
- Prefer
transformandopacity. Rotating, scaling, moving and fading are the properties browsers optimise hardest. Almost all icon animation can be expressed with them. - Avoid animating layout properties. Changing width, height, top, left or margin makes the browser recompute the page geometry each frame. Use a
transform: scale()ortranslate()instead of resizing or repositioning by layout. - Keep it short. Interface animation should feel instant. Most icon transitions land well between 150 and 400 milliseconds. Longer than that starts to feel sluggish.
- Do not animate dozens of icons at once. A wall of simultaneously moving icons is both a performance cost and a visual mess. Animate the one that matters.
Common mistakes
- Trying to animate an
imgor background SVG with CSS. Your stylesheet cannot reach inside a referenced SVG. Inline it, or use SMIL inside the file. - Rotation spinning off-centre. The transform-origin trap. Set
transform-origin: centerand, if needed,transform-box: fill-box. - Using the draw-on effect on a filled shape. Line drawing needs a stroked path. A solid fill has no stroke to reveal, so the technique does nothing.
- Ignoring reduced-motion preferences. Looping or large animation with no
prefers-reduced-motionfallback can genuinely make some users unwell. - Reaching for a JavaScript library too early. A spinner does not need a dependency. Add weight only when the animation is complex enough to justify it.
- Looping attention-grabbing motion forever. A notification shake or a bouncing arrow should play once or a few times, then stop. Endless motion is fatiguing.
Frequently asked questions
Put it into practice: grab an animated icon from the tool, learn the ways to place an SVG, recolour it to match your brand, and make sure it stays accessible and lightweight before it ships.