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>
ElementAnimatesKey attributes
animateAny attribute or CSS propertyattributeName, from, to, values, calcMode
animateTransformA transformtype plus the same timing attributes; use additive="sum" to stack
animateMotionPosition along a pathpath or a referenced mpath, rotate="auto"
setA value with no interpolationto plus begin
timingShared by all of thembegin, 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, so stroke-dasharray: 1 works 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

MethodStylable / scriptableBest for
Inline <svg> in the HTMLYes — CSS and JS reach everythingIcons and diagrams that react to state
<img src="file.svg">No — a separate documentSimple, cacheable artwork; can still be described with alt
CSS background-imageNoPurely decorative texture; invisible to assistive technology
<object> or <iframe>Only by script inside the fileLarge interactive documents
External sprite plus usePartially — CSS inherits, shadow DOM does notIcon 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.

Styling with CSS and paint servers Media and embedding

Last refreshed 2026-09-18.