// blog/developer/
Back to Blog
Developer · Published August 2, 2026 · 7 min read · By Toine ·

Update note: Rewritten from experience; path morphing browser support and GSAP licensing updated, SVG Optimizer linked, two decorative tool links removed

SVG Animation with CSS: Transitions, Keyframes, the Line-Draw Trick, and the Rules That Keep It Shippable

SVG Animation with CSS: Transitions, Keyframes, the Line-Draw Trick, and the Rules That Keep It Shippable

A spinner, a check mark that draws itself, a chart whose bars grow in, a hamburger icon that turns into an X: all of that ships as SVG and CSS, with no video, no library and no JavaScript beyond, at most, one line to measure a path. The file is vector, so it is sharp at any size; a simple icon is under 5 KB; and every element in it can carry an ARIA attribute, which matters more than most animation guides admit.

If you have animated an HTML element you already know most of this. transition, @keyframes and animation work the same on an SVG element. The parts that are new are the SVG-only properties, stroke-dasharray and stroke-dashoffset for the line-drawing effect, and one default, transform-origin, that is different in SVG and catches everyone the first time. I test web applications for a living, and animation is where I log a specific kind of defect: motion that is the only signal of a state, and motion that ignores the user's reduced-motion setting. Both are in the rules at the end, and I would rather you read those first.

* * *

The minimum transition, and which SVG properties will tween

`svg `

`css .pulse { transition: r 0.3s ease, fill 0.3s ease; } .pulse:hover { r: 45; fill: #3b82f6; } `

On hover the circle grows and lightens. r is a presentation attribute that current browsers also accept as a CSS property, so it transitions like anything else. The SVG properties that tween cleanly:

  • fill and stroke, the colours
  • stroke-width
  • opacity
  • r, cx and cy on circles, x, y, width and height on rects
  • transform (rotate, scale, translate)

Set the property, a duration and an easing, and the browser interpolates between the two states, exactly as it would for a

. When the stylesheet is done, the CSS Minifier takes 30 to 40% off a file full of keyframe blocks, which are verbose by nature.

Animated SVG graphics on web page
Animated SVG graphics on web page
* * *

The line-drawing effect: one dash as long as the path, then slide it into view

The trick that makes a logo or a check mark appear to be drawn by hand uses two properties.

stroke-dasharray makes a stroke dashed. Set it to the total length of the path and the whole path becomes a single dash with a gap the same length behind it. stroke-dashoffset slides that pattern along the path. At the full length, the gap covers the path and nothing is visible; at zero, the dash covers it and the whole line shows. Animate the offset from length to zero and the line draws itself.

`svg `

`css .draw { stroke-dasharray: 300; stroke-dashoffset: 300; animation: draw 2s ease forwards; }

@keyframes draw { to { stroke-dashoffset: 0; } } `

forwards keeps the final frame when the animation ends; without it the line vanishes again. The number has to be at least the real path length, and guessing it is the usual cause of a draw that stops short or starts late. Measure it once in the browser console: document.querySelector('.draw').getTotalLength(). Or set both values to a figure you know is larger than the path, at the cost of the animation starting with a pause.

The effect works on any stroked element: path, circle, rect, polyline, text. For a sequence, give each path an animation-delay equal to the previous path's duration, so the second line starts where the first stops.

Key takeaway

The trick that makes a logo or a check mark appear to be drawn by hand uses two properties.

* * *

Keyframes, and the transform-origin default that breaks every first spinner

@keyframes is unchanged from HTML.

Spinner:

`css .spinner { animation: spin 1s linear infinite; transform-origin: center; }

@keyframes spin { to { transform: rotate(360deg); } } `

Pulsing dot:

`css .pulse { animation: pulse 1.5s ease-in-out infinite; }

@keyframes pulse { 0%, 100% { r: 5; opacity: 1; } 50% { r: 10; opacity: 0.5; } } `

Colour cycle:

`css .morph { animation: colors 4s ease infinite; }

@keyframes colors { 0% { fill: #ef4444; } 33% { fill: #22c55e; } 66% { fill: #3b82f6; } 100% { fill: #ef4444; } } `

The line in the spinner that matters is transform-origin: center. In HTML the origin of a transform defaults to the centre of the element. In SVG it defaults to the origin of the coordinate system, the top-left corner of the viewport, so a rotation without that line swings the shape around the corner of the drawing instead of turning in place. For anything nested inside a group, add transform-box: fill-box as well, so the origin is measured against the element's own box and not the whole SVG. Safari has been the browser most likely to disagree about this, and it is the first place I would look when a rotation is off-centre in one browser and fine in the others.

* * *

Six patterns worth copying

  • Hamburger to X: three elements. Rotate the top one 45 degrees and the bottom one minus 45, fade the middle one out. One class toggled on click.
  • Progress bar: a whose width animates from 0 to the target, rx="5" for rounded ends, and a text element updated alongside it.
  • Chart bars: several elements with height animated from 0, staggered by 100 ms each. Move y as the height grows so the bars rise from the baseline instead of dropping from the top.
  • Wave background: one sine-wave , duplicated and offset, each copy translated on the x-axis at a slightly different speed.
  • Check mark confirmation: a drawn with the dasharray trick, on a green circle that scales up from 0 behind it. The standard form-success pattern.
  • Typing cursor: a blinking on opacity with steps(1) as the timing function.

All six are CSS only. The two I would actually put on a page are the progress bar and the check mark, because both tell the user something. The others are for when a designer asks.

Designer creating vector animation in editor
Designer creating vector animation in editor
* * *

The performance and accessibility rules I would fail a build on

Performance:

  • Animate transform and opacity wherever you can. Both are composited without a layout pass. Animating width, height, cx, cy or r forces layout on every frame, which is fine for one element and not for forty.
  • will-change: transform on the few elements that move, and only those. Applied everywhere it costs memory for nothing.
  • Keep simultaneous animations to a dozen or so. More moving SVG elements than that drops frames on a mid-range phone.
  • Optimise the file before animating it. Fewer path commands means cheaper repaints. The SVG Optimizer strips editor metadata, comments, unused attributes and whitespace in the browser; for path simplification and decimal precision the SVGO command line goes further. A clean animated SVG is 2 to 10 KB.

Accessibility:

  • Respect the user's setting:

`css @media (prefers-reduced-motion: reduce) { .animated { animation: none; } } `

  • Animation is never the only signal of a state. A spinner without aria-busy="true" or visible text is a defect, and it is one I log in acceptance testing every time I find it, because a screen reader user is left with a form that does nothing.
  • Decorative SVGs get aria-hidden="true". SVGs that carry information get role="img" and an aria-label.
  • No infinite loops on things that are not loaders. Constant motion in the corner of the page is hostile to anyone with an attention disorder and irritating to everyone else.
  • Test it with a screen reader once. It takes ten minutes and it is the only way to know whether the SVG announces something useful or nothing at all.
* * *

FAQ

Can SVG paths morph between shapes with CSS?

Only when both paths have the same number and type of commands, and only in browsers that accept d as a CSS property, which Chrome and Firefox do and Safari has been slow to. Check the current support table before relying on it. For anything else, GSAP's MorphSVG plugin (free since GSAP went free in 2025) or Flubber normalise the two paths first and interpolate reliably.

CSS or JavaScript for SVG animation?

CSS for transitions, rotation, colour and stroke effects. JavaScript, meaning GSAP, Motion (formerly Motion One) or the Web Animations API, for sequences with many steps, morphing, physics-style motion, and anything that has to follow user input.

Why does my animation look different in Safari?

Nearly always transform-origin. Add transform-box: fill-box, set the origin in explicit pixels rather than percentages, and test there before calling it done.

How do I make an animated SVG smaller?

Run it through the SVG Optimizer for the metadata and editor leftovers, then SVGO on the command line for path simplification and decimal precision, move inline styles into classes, and delete the elements the export left behind. Most animated icons end up between 2 and 10 KB.

Key takeaway

### Can SVG paths morph between shapes with CSS.