Appearance
Animated SVGs
Replace animated GIFs with SVG files that use CSS keyframe animations and custom properties for parametric control. The result is resolution-independent, smaller, and fully customizable.
When to use
Replace animated GIFs with SVG wherever you need resolution-independent, customizable animations — loading spinners, progress indicators, decorative loops, or any graphic that benefits from parametric control over timing, color, and size.
The pattern
Using an animated SVG in HTML
Point an <img> tag at the SVG file. The embedded CSS animations run automatically:
html
<img src="./spinner.svg" alt="Loading…" />Or inline the SVG directly for currentColor inheritance and styling access:
html
<div style="color: #333;">
<svg xmlns="http://www.w3.org/2000/svg" ...>
<!-- SVG content -->
</svg>
</div>Implementing a spinner
Embed a <style> block inside the SVG. Define a @keyframes animation and apply it to elements using CSS custom properties for parametric values like rotation offset and delay:
xml
<svg
xmlns="http://www.w3.org/2000/svg"
width="48px"
height="48px"
viewBox="0 0 100 100"
preserveAspectRatio="xMidYMid"
>
<style>
@keyframes pulse {
0% { opacity: 1; }
100% { opacity: 0; }
}
rect {
width: 6px;
height: 12px;
x: 48px;
y: 24px;
transform: rotate(calc(var(--n) * 360deg / 12));
transform-origin: center center;
rx: 3px;
ry: 3px;
fill: currentColor;
animation: pulse 1s infinite ease-out;
animation-delay: calc(-1s * var(--n) / 12);
}
</style>
<rect style="--n: 1"/>
<rect style="--n: 2"/>
<rect style="--n: 3"/>
<rect style="--n: 4"/>
<rect style="--n: 5"/>
<rect style="--n: 6"/>
<rect style="--n: 7"/>
<rect style="--n: 8"/>
<rect style="--n: 9"/>
<rect style="--n: 10"/>
<rect style="--n: 11"/>
<rect style="--n: 12"/>
</svg>How the spinner works
@keyframes pulsefades each rectangle from fully opaque to transparent.--ncustom property on each<rect>sets its position around the circle usingrotate(calc(var(--n) * 360deg / 12)).animation-delay: calc(-1s * var(--n) / 12)offsets each bar's animation so they pulse in sequence, creating the spinning effect.fill: currentColorinherits the text color from the embedding context, making the spinner adapt to light and dark themes.
Implementing a pulse ring
Use calc() with custom properties to control the number of elements and their distribution:
xml
<svg
xmlns="http://www.w3.org/2000/svg"
width="64px"
height="64px"
viewBox="0 0 100 100"
>
<style>
@keyframes fade {
0%, 100% { opacity: 0.2; }
50% { opacity: 1; }
}
circle.dot {
cx: 50;
cy: 15;
r: 4;
fill: currentColor;
transform: rotate(calc(var(--i) * 360deg / 8));
transform-origin: 50px 50px;
animation: fade 1.2s infinite ease-in-out;
animation-delay: calc(var(--i) * 1.2s / 8);
}
</style>
<circle class="dot" style="--i: 0"/>
<circle class="dot" style="--i: 1"/>
<circle class="dot" style="--i: 2"/>
<circle class="dot" style="--i: 3"/>
<circle class="dot" style="--i: 4"/>
<circle class="dot" style="--i: 5"/>
<circle class="dot" style="--i: 6"/>
<circle class="dot" style="--i: 7"/>
</svg>Implementing a breathing icon
This example uses a single-element animation with no custom properties:
xml
<svg
xmlns="http://www.w3.org/2000/svg"
width="32px"
height="32px"
viewBox="0 0 100 100"
>
<style>
@keyframes breathe {
0%, 100% { r: 20; opacity: 0.6; }
50% { r: 35; opacity: 1; }
}
circle {
cx: 50;
cy: 50;
fill: currentColor;
animation: breathe 2s infinite ease-in-out;
}
</style>
<circle />
</svg>Trade-offs
| Approach | Pros | Cons |
|---|---|---|
| Animated SVG | Crisp, tiny, themeable | No raster effects |
| Animated GIF | Universal support | Large, fixed resolution |
| CSS on HTML | Full DOM access | Not portable |
| Lottie / JS | Complex animations | Runtime dependency |
Prefer animated SVGs for simple looping UI animations like spinners, pulsing indicators, and icon effects. Use Lottie or a JS library only when the animation requires complex multi-stage choreography or imported motion design.