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:
fillandstroke, the coloursstroke-widthopacityr,cxandcyon circles,x,y,widthandheighton rectstransform(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.
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.
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
whosewidthanimates from 0 to the target,rx="5"for rounded ends, and a text element updated alongside it. - Chart bars: several
elements withheightanimated from 0, staggered by 100 ms each. Moveyas 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 onopacitywithsteps(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.

The performance and accessibility rules I would fail a build on
Performance:
- Animate
transformandopacitywherever you can. Both are composited without a layout pass. Animatingwidth,height,cx,cyorrforces layout on every frame, which is fine for one element and not for forty. will-change: transformon 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 getrole="img"and anaria-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.
### Can SVG paths morph between shapes with CSS.
Debugging Minified Code: Pretty-Print for Today, Source Maps for Every Day After
A production error that points at bundle.js line 1, column 48213 is not a defect report anyone can act on. How to pretty-print in DevTools right now, how to set up source maps so it never happens again, and how to keep them private.
Semantic HTML and SEO: What the Tags Do and Do Not Buy You
Google does not rank a page higher for using article instead of div. Here is what semantic elements actually change for crawlers and screen readers, the mistakes that make things worse, and a template that gets the structure right.
CSS Generators: Gradients, Flexbox, Grid and Shadows
Use CSS generators to create gradients, flexbox layouts, grid systems, and box shadows. Visual editors output ready-to-copy CSS for your project.
Glassmorphism in CSS: The Frosted Glass Effect Done Right
The four-line backdrop-filter recipe for frosted glass, the dark mode version, how many glass layers a phone can take, the contrast check most glass UIs fail, and the pages where I would not use it at all.
