Animation, embedding and accessibility
SMIL timing, CSS keyframes inside the document, the four ways to embed an SVG, and how to make graphics readable by assistive technology.
SMIL animation
<svg viewBox="0 0 200 80">
<rect x="0" y="30" width="40" height="20" fill="#4f46e5">
<animate attributeName="x" from="0" to="160" dur="2s"
repeatCount="indefinite" calcMode="spline"
keySplines="0.4 0 0.2 1" />
</rect>
<g>
<circle cx="100" cy="40" r="12" fill="#f59e0b" />
<animateTransform attributeName="transform" type="rotate"
from="0 100 40" to="360 100 40" dur="3s"
repeatCount="indefinite" />
</g>
<circle r="6" fill="#0f766e">
<animateMotion dur="4s" repeatCount="indefinite" fill="freeze"
path="M10 40 C 60 0, 140 80, 190 40" />
</circle>
</svg>| Element | Animates | Key attributes |
|---|---|---|
animate | Any attribute or CSS property | attributeName, from, to, values, calcMode |
animateTransform | A transform | type plus the same timing attributes; use additive="sum" to stack |
animateMotion | Position along a path | path or a referenced mpath, rotate="auto" |
set | A value with no interpolation | to plus begin |
| timing | Shared by all of them | begin, dur, repeatCount, fill="freeze" |
Timing can be chained declaratively: begin="other.end+0.2s" or begin="click" on a parent group, which gives you coordinated sequences without a line of JavaScript. Every current browser except Internet Explorer supports SMIL, although the specification work has moved toward CSS and the Web Animations API.
CSS animation inside the SVG
<style>
.pulse { animation: pulse 2s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.75; }
}
.draw {
fill: none; stroke: #4f46e5; stroke-width: 3;
stroke-dasharray: 1; stroke-dashoffset: 1;
animation: draw 1.4s ease-out forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
@media (prefers-reduced-motion: reduce) {
.pulse, .draw { animation: none; }
.draw { stroke-dashoffset: 0; }
}
</style>
<svg viewBox="0 0 200 80">
<path class="draw" pathLength="1" d="M10 60 C 50 0, 150 0, 190 60" />
<circle class="pulse" cx="100" cy="40" r="10" fill="#f43f5e" />
</svg>pathLength="1"normalises the path length, sostroke-dasharray: 1works for any shape — this is the standard draw-on effect.- CSS animations run on the compositor for transform and opacity, which is smoother than SMIL for those properties.
- Respect
prefers-reduced-motion: switch the animation off and leave the final state visible, not the initial one.
Embedding and accessibility
| Method | Stylable / scriptable | Best for |
|---|---|---|
Inline <svg> in the HTML | Yes — CSS and JS reach everything | Icons and diagrams that react to state |
<img src="file.svg"> | No — a separate document | Simple, cacheable artwork; can still be described with alt |
CSS background-image | No | Purely decorative texture; invisible to assistive technology |
<object> or <iframe> | Only by script inside the file | Large interactive documents |
External sprite plus use | Partially — CSS inherits, shadow DOM does not | Icon systems with many repeated glyphs |
<!-- meaningful graphic: name it -->
<svg viewBox="0 0 24 24" role="img" aria-labelledby="chartTitle chartDesc">
<title id="chartTitle">Quarterly revenue</title>
<desc id="chartDesc">Revenue rose from 30 to 60 thousand across four quarters.</desc>
<path d="M2 20 L8 12 L14 15 L22 4" fill="none" stroke="currentColor" stroke-width="2" />
</svg>
<!-- decorative: hide it from the accessibility tree -->
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M4 12 l6 6 L20 6" fill="none" stroke="currentColor" stroke-width="2" />
</svg>
<!-- icon-only button: the label belongs on the button -->
<button type="button" aria-label="Close dialog">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M5 5 L19 19 M19 5 L5 19" stroke="currentColor" stroke-width="2" />
</svg>
</button>💡
An inline SVG with no
role is announced as an unnamed graphic in some screen readers. Add role="img" plus a <title> when the picture carries meaning, and aria-hidden="true" when it does not — there is no useful middle ground.FAQ
Is SMIL still safe to use?
For browser rendering, yes: Chrome, Firefox and Safari all support it, and Chrome reversed its deprecation plan years ago. For long-lived code, CSS animations or the Web Animations API are the better bet because they are where the specifications and tooling moved.
How do I recolour an SVG used as an image?
You cannot — the stylesheet does not cross the document boundary. Either inline the SVG, build it as a CSS
mask-image so the colour comes from background-color, or ship a second file.Related
Styling with CSS and paint servers Media and embedding
Last refreshed 2026-09-18.