SMIL's Browser Support in 2026

Older articles warn against SMIL: Chrome 1 announced an intent to deprecate it in 2015, and Internet Explorer and the original Edge never supported it. The deprecation never happened, and since Edge moved to Chromium 4,389 in January 2020 SMIL has been Baseline everywhere. Declarative animation also runs where scripts cannot: in a Chrome 154 test, an <img> ran a <set> and a CSS @keyframes inside its SVG, but not its <script>.

The four ways to animate SVG
Technique What it animates Runs inside <img> Control
SMIL elements Any animatable attribute, including points and d Yes Event timing, seeking
CSS transitions and animations CSS properties: fill, stroke-*, transform, r, cx, ... Yes Media queries, classes
Web Animations API The same CSS properties No Play, pause, seek, reverse
requestAnimationFrame loop Anything, including data-driven geometry No Full, and all the code

Prefer CSS for state changes, where @media (prefers-reduced-motion: reduce) can switch motion off; use SMIL for self-contained animated files and path morphing (CSS d works only in Chromium and Firefox 555 ).

The four ways to animate SVG side by side: SMIL, CSS, Web Animations and requestAnimationFrameHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 13px system-ui, sans-serif; background: #fafaf7; }
  svg { width: 100%; max-width: 600px; display: block; }
  .panel { fill: #fff; stroke: #d8d4c8; }
  .title { font-weight: 700; font-size: 14px; }
  .sub { font-size: 11px; fill: #6b665c; }
  /* 2. CSS keyframes: animates CSS properties such as transform and fill */
  .css-book { transform-box: fill-box; transform-origin: center; animation: tilt 2s ease-in-out infinite alternate; }
  @keyframes tilt { from { transform: rotate(-12deg); fill: #e07a5f; } to { transform: rotate(12deg); fill: #3d5a80; } }
  @media (prefers-reduced-motion: reduce) { .css-book { animation: none; } }
</style>
<svg viewBox="0 0 600 300">
  <!-- 1. SMIL: morphs path data (d) declaratively; also works inside <img> -->
  <rect class="panel" x="5" y="5" width="142" height="290" rx="8"/>
  <text class="title" x="16" y="28">SMIL</text>
  <path fill="#81b29a" d="M30 200 L76 110 L76 110 L122 200 Z">
    <animate attributeName="d" dur="2s" repeatCount="indefinite"
      values="M30 200 L76 110 L76 110 L122 200 Z; M30 200 L30 110 L122 110 L122 200 Z; M30 200 L76 110 L76 110 L122 200 Z"
      calcMode="spline" keySplines="0.4 0 0.6 1; 0.4 0 0.6 1"/>
  </path>
  <text class="sub" x="16" y="240">any attribute, incl. d</text>
  <text class="sub" x="16" y="256">runs inside &lt;img&gt;: yes</text>

  <!-- 2. CSS animation -->
  <rect class="panel" x="153" y="5" width="142" height="290" rx="8"/>
  <text class="title" x="164" y="28">CSS</text>
  <rect class="css-book" x="194" y="110" width="60" height="90" rx="4"/>
  <text class="sub" x="164" y="240">CSS properties only</text>
  <text class="sub" x="164" y="256">runs inside &lt;img&gt;: yes</text>

  <!-- 3. Web Animations API -->
  <rect class="panel" x="301" y="5" width="142" height="290" rx="8"/>
  <text class="title" x="312" y="28">Web Animations</text>
  <circle id="waapi" cx="372" cy="155" r="30" fill="#f2cc8f" stroke="#3d5a80" stroke-width="4"/>
  <text class="sub" x="312" y="240">play / pause / reverse</text>
  <text class="sub" x="312" y="256">click the circle</text>

  <!-- 4. requestAnimationFrame -->
  <rect class="panel" x="449" y="5" width="146" height="290" rx="8"/>
  <text class="title" x="460" y="28">rAF loop</text>
  <polyline id="wave" fill="none" stroke="#e07a5f" stroke-width="3"/>
  <text class="sub" x="460" y="240">data-driven geometry</text>
  <text class="sub" x="460" y="256">full control, all code</text>
</svg>
<script>
  // 3. WAAPI: a scripted animation you can control
  const circle = document.getElementById('waapi');
  const anim = circle.animate(
    [{ transform: 'translateY(-40px)' }, { transform: 'translateY(40px)' }],
    { duration: 900, iterations: Infinity, direction: 'alternate', easing: 'ease-in-out' });
  circle.addEventListener('click', () =>
    anim.playState === 'running' ? anim.pause() : anim.play());

  // 4. rAF: recompute the points of a sine wave every frame
  const wave = document.getElementById('wave');
  function frame(t) {
    const pts = [];
    for (let x = 0; x <= 120; x += 6) pts.push(`${462 + x},${155 + 35 * Math.sin(x / 18 + t / 300)}`);
    wave.setAttribute('points', pts.join(' '));
    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);
</script>