Back to Learn SVG Animation

SVG Animation with the View Transition API: 2026 Guide

By Published Updated tools and workflows

Use the View Transition API to snapshot an inline SVG before and after a UI change, then animate the named snapshot with CSS. In 2026, same-document transitions work across current browsers; cross-document transitions remain a progressive enhancement because Firefox does not yet support the CSS opt-in.

When should you use a view transition for SVG?

Use the View Transition API when an SVG represents the same conceptual object before and after a user-interface change. Good examples include an icon changing state, a selected diagram moving into a detail view, a logo continuing between pages, or an illustration changing size and position as a panel opens.

The API is not a general replacement for SVG animation. It does not interpolate path data, draw strokes, or create continuous motion by itself. It captures the old and new rendered states, pairs snapshots with the same view-transition-name, and animates those snapshots with CSS.

  • Use it for continuity between UI states, routes, layouts, or documents.
  • Use CSS or the Web Animations API for animation within one stable SVG state.
  • Use a path-morphing tool when the actual d commands must interpolate.
  • Use stroke animation when the intended effect is a line being drawn. See how SVG line drawing animation works.

A useful mental model is: the SVG changes immediately in the real DOM, while the browser animates a visual bridge between the old and new renderings.

What the View Transition API actually captures

For a same-document transition, document.startViewTransition() first captures the old state. It then runs your update callback, captures the new state, and creates a temporary tree of pseudo-elements. The old capture is a static image; the new capture represents the new interactive state.

A named SVG normally produces pseudo-elements with this shape:

::view-transition
└─ ::view-transition-group(weather-icon)
   └─ ::view-transition-image-pair(weather-icon)
      ├─ ::view-transition-old(weather-icon)
      └─ ::view-transition-new(weather-icon)

The pseudo-element tree is visual and is not added to the accessibility tree. The semantic SVG and controls in the real DOM remain the source of accessible names, focus, and interaction. This separation is useful: you do not need to keep duplicate old and new SVG markup in the document just to create an effect.

Because the captures behave like rendered images, view transitions can cross-fade an SVG, move it, scale it, clip it, and animate its snapshot. They do not expose individual captured paths to selectors such as fill or stroke. Make those SVG changes in the DOM update callback, then use the transition pseudo-elements to control how the two rendered states connect.

Browser support in July 2026

Same-document transitions

Same-document view transitions are the dependable starting point. MDN marks document.startViewTransition() and view-transition-name as Baseline 2025, newly available across current browser engines since October 2025. Chrome has supported the core same-document API since Chrome 111, Safari since Safari 18, and Firefox added SPA view transitions in Firefox 144.

Older browsers and embedded webviews still exist, so production code should feature-detect document.startViewTransition. The fallback is simply to perform the DOM update immediately.

Cross-document transitions

Cross-document transitions use the CSS @view-transition rule. They are supported in Chromium browsers from version 126 and Safari from 18.2. As of July 23, 2026, MDN still marks this capability as limited availability because Firefox does not provide the production cross-document opt-in.

That support gap is acceptable when the effect is progressive enhancement. Firefox and older browsers follow the link normally. Navigation, content, and task completion must never depend on the animation.

Build a same-document SVG transition

This example changes an inline weather icon between day and night. It keeps the button that received focus in place, updates the SVG’s accessible description, and uses a normal immediate update when view transitions are unavailable or reduced motion is requested.

1. Start with semantic controls and one inline SVG

<section class="weather-picker" aria-labelledby="weather-heading">
  <h2 id="weather-heading">Preview an icon state</h2>

  <div class="weather-actions" role="group" aria-label="Icon state">
    <button type="button" data-state-button="day" aria-pressed="true">
      Day
    </button>
    <button type="button" data-state-button="night" aria-pressed="false">
      Night
    </button>
  </div>

  <svg
    id="weather-icon"
    class="weather-icon"
    data-state="day"
    viewBox="0 0 160 160"
    width="160"
    height="160"
    role="img"
    aria-labelledby="weather-title weather-desc"
  >
    <title id="weather-title">Day weather icon</title>
    <desc id="weather-desc">A sun above a calm horizon.</desc>

    <g class="scene scene-day">
      <circle class="sun" cx="80" cy="62" r="28" />
      <path class="horizon" d="M28 116H132" />
    </g>

    <g class="scene scene-night">
      <path class="moon" d="M101 37A37 37 0 1 0 119 98A31 31 0 1 1 101 37Z" />
      <circle class="star" cx="42" cy="48" r="4" />
      <circle class="star" cx="126" cy="61" r="3" />
    </g>
  </svg>
</section>

The outer svg has explicit dimensions and a stable viewBox. That gives both captures the same CSS box and coordinate system. The SVG also has one accessible name and description; individual decorative shapes do not need separate labels.

2. Name the SVG and animate its snapshots

:root {
  view-transition-name: none;
}

.weather-icon {
  view-transition-name: weather-icon;
}

.scene {
  opacity: 0;
}

.weather-icon[data-state="day"] .scene-day,
.weather-icon[data-state="night"] .scene-night {
  opacity: 1;
}

.sun {
  fill: #f6b73c;
}

.moon,
.star {
  fill: #dfe8ff;
}

.horizon {
  fill: none;
  stroke: currentColor;
  stroke-width: 6;
  stroke-linecap: round;
}

@keyframes icon-out {
  to {
    opacity: 0;
    transform: scale(0.94);
  }
}

@keyframes icon-in {
  from {
    opacity: 0;
    transform: scale(1.06);
  }
}

::view-transition-old(weather-icon) {
  animation: 160ms ease-in both icon-out;
}

::view-transition-new(weather-icon) {
  animation: 260ms ease-out both icon-in;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Setting the root transition name to none avoids capturing and cross-fading the whole page when only the icon needs continuity. The SVG gets one independent, stable name. The old and new snapshots then receive short opacity and scale animations.

3. Put the complete state change inside the update callback

const icon = document.querySelector("#weather-icon");
const title = document.querySelector("#weather-title");
const description = document.querySelector("#weather-desc");
const buttons = document.querySelectorAll("[data-state-button]");
const reducedMotion = window.matchMedia(
  "(prefers-reduced-motion: reduce)"
);

function applyState(nextState) {
  const isNight = nextState === "night";

  icon.dataset.state = nextState;
  title.textContent = isNight
    ? "Night weather icon"
    : "Day weather icon";
  description.textContent = isNight
    ? "A crescent moon and two stars."
    : "A sun above a calm horizon.";

  buttons.forEach((button) => {
    const isSelected = button.dataset.stateButton === nextState;
    button.setAttribute("aria-pressed", String(isSelected));
  });
}

function selectState(nextState) {
  const update = () => applyState(nextState);

  if (!document.startViewTransition || reducedMotion.matches) {
    update();
    return;
  }

  const transition = document.startViewTransition(update);

  transition.ready.catch(() => {
    // The DOM update still completes if the visual transition is skipped.
  });
}

buttons.forEach((button) => {
  button.addEventListener("click", () => {
    selectState(button.dataset.stateButton);
  });
});

The fallback calls the same update function, so supported and unsupported browsers reach an identical final state. Reduced-motion users avoid the capture work as well as the animation. If the transition is skipped, the API still performs the DOM update; the effect is never allowed to control application state.

Why the outer SVG is the safest capture target

The view-transition-name property applies broadly, but the specification requires a rendered principal box for participation. An inline outer svg or a stable HTML wrapper provides a predictable CSS box, includes all descendant paths in one capture, and is easier to size consistently across engines.

For production work, prefer one of these targets:

  • The outer inline svg when the complete illustration changes as one unit.
  • An HTML figure, link, or card wrapper when the SVG moves with text or other content.
  • A small number of meaningful SVG groups only after testing the exact browser baseline you support.

Avoid assigning transition names to every path. Every independently named element creates another capture and pseudo-element group, increases the chance of naming collisions, and makes the effect harder to debug. Most icon transitions need one capture, not dozens.

SVG-specific pitfalls that cause broken transitions

A view transition is not path morphing

If one state contains a circle and the next contains a star, the default result is a cross-fade between rendered captures. The API does not make their path commands compatible or interpolate the d attribute. Use a dedicated morphing technique when continuous geometric transformation is essential. The comparison in CSS vs GSAP for SVG can help choose the appropriate control model.

Different viewBox or aspect ratios can look like distortion

The browser animates the capture’s CSS position, transform, width, and height. If the old SVG is square and the new one is wide, or if their viewBox and preserveAspectRatio rules differ, the visual content may scale, crop, or appear to jump inside the moving box.

Keep these stable whenever possible:

  • CSS width, height, and aspect-ratio.
  • The outer SVG’s viewBox.
  • The preserveAspectRatio behavior.
  • Margins and layout constraints around the capture target.

If two layouts intentionally use different proportions, test the intermediate frames rather than judging only the start and end screenshots.

Every rendered transition name must be unique

If two visible elements have the same custom view-transition-name at the same time, the transition’s ready promise rejects and the visual transition is skipped. This often happens when a reusable icon component gives every instance a name such as icon.

Use one explicit name for a single featured SVG. For repeated same-document items, view-transition-name: match-element can generate identity-based names in current browsers. Do not use match-element to pair content across documents: element identity does not transfer from one document to another.

The pseudo-elements are not descendants of the SVG

A selector such as .weather-icon::view-transition-old() is not the right model. View transition pseudo-elements belong to the transition tree rooted at the transition scope. Target the name directly with selectors such as ::view-transition-old(weather-icon).

Similarly, changing fill on the snapshot pseudo-element does not recolor individual captured paths. Change SVG presentation in the real DOM, and animate the resulting snapshot with properties suited to an image-like layer.

Filters, masks, shadows, and overflow enlarge capture work

The capture includes rendered effects such as opacity and filters on the named element and its descendants. A large blur, glow, drop shadow, or filter region can greatly expand the ink overflow area. Implementations may clip extremely large capture areas or reduce rasterization quality.

Keep filter regions deliberate, avoid naming a huge offscreen illustration when only one icon changes, and test at high device-pixel ratios. A visual effect that is inexpensive on the live vector may still require a large temporary raster capture.

Late resources produce inconsistent snapshots

An external SVG sprite, web font, image pattern, or CSS file that finishes loading after the new state is captured can cause a flash or mismatched transition. Do not fetch essential visual state inside a same-document update callback. Preload required assets and keep the callback short.

For cross-document transitions, ensure the destination’s critical SVG and layout are available for its first render. The platform also provides <link rel="expect"> for advanced cases where a destination must delay rendering until a critical element is parsed, but it should be used selectively and tested against the project’s browser baseline.

Use SVG view transitions across pages

Cross-document view transitions require no call to document.startViewTransition(). Put the opt-in in a stylesheet loaded by both pages, give the corresponding SVG a stable name on both pages, and navigate normally.

@view-transition {
  navigation: auto;
}

.featured-icon {
  view-transition-name: featured-icon;
}

::view-transition-group(featured-icon) {
  animation-duration: 280ms;
  animation-timing-function: ease-out;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Both documents can use different elements as long as exactly one rendered element on each side has the name featured-icon. For example, a catalog page can name its single featured thumbnail, while the destination page uses the same name on the large illustration.

A cross-document transition is eligible only when:

  • The current and destination documents are same-origin, including scheme, hostname, and port.
  • Both documents opt in with @view-transition { navigation: auto; }.
  • There is no intermediate cross-origin redirect.
  • The navigation is an eligible push, replace, or history traversal.
  • The browser supports cross-document view transitions.

Address-bar navigation, bookmarks, and reloads do not use the same transition path. Chrome also skips a transition if the destination takes longer than its allowed transition window, documented as four seconds. None of these cases should be treated as an application error; users simply receive an ordinary navigation.

Accessibility: preserve meaning before adding motion

View transitions are visual enhancement, not a substitute for accessible SVG markup. Give an informative SVG a concise accessible name using title, desc, or an appropriate ARIA label, and hide decorative SVGs from assistive technology. The detailed patterns in Accessible SVG: title, desc, ARIA labels, and decorative icons cover those choices.

For a same-document state change:

  • Keep the triggering button or control keyboard accessible.
  • Use state attributes such as aria-pressed when they communicate a real selection.
  • Update the SVG’s accessible name or description in the same callback as its visual state.
  • Do not move focus merely because an illustration moved visually.
  • Announce meaningful asynchronous content changes separately when needed; an animation is not an announcement.

Respect prefers-reduced-motion at both levels. JavaScript can bypass same-document capture for people requesting less motion, while CSS can suppress or replace cross-document animation. A small dissolve may be acceptable in some products, but zooming, large scaling, spatial slides, and parallax-like movement should be removed or substantially reduced. See the site’s full prefers-reduced-motion implementation guide.

Performance rules for production SVG transitions

  1. Name only what needs independent continuity. Every named capture consumes rendering and memory resources. One outer SVG is usually enough.
  2. Disable the root capture when the page itself should not transition. Using :root { view-transition-name: none; } prevents an unnecessary full-page snapshot in the same-document icon example.
  3. Keep geometry stable. Stable boxes avoid expensive and visually awkward width and height interpolation. Prefer transform and opacity effects on the transition snapshots.
  4. Keep the update callback synchronous and short. Update attributes, classes, or local DOM immediately. Do not wait for a network request while rendering is paused for capture.
  5. Control SVG filter bounds. Oversized glow and blur regions can produce very large raster areas.
  6. Keep durations purposeful. About 150–300 milliseconds is enough for most icon and component continuity. Long transitions delay visual settlement without improving comprehension.
  7. Test representative hardware. A dense SVG with masks and filters can behave differently on a high-density phone than on a development laptop.

View transitions can reduce the amount of application code needed to coordinate two visual states, but they do not make heavy rendering free. Use the checks in SVG animation performance best practices alongside browser performance tooling.

How to test and debug the transition

Test the final state without animation first

Disable the API or run the reduced-motion path and confirm that every control, SVG label, route, and layout reaches the correct state. If the non-animated experience fails, the transition architecture is wrong.

Inspect promise failures during development

function runDebugTransition(update) {
  const transition = document.startViewTransition(update);

  transition.updateCallbackDone.catch((error) => {
    console.error("The DOM update failed:", error);
  });

  transition.ready.catch((error) => {
    console.warn("The visual transition was skipped:", error);
  });

  transition.finished.catch((error) => {
    console.error("The transition did not finish:", error);
  });

  return transition;
}

A rejected ready promise commonly reveals duplicate names, an invalid rendering state, or a transition that was intentionally skipped. A rejected updateCallbackDone points to the application’s update logic rather than the CSS animation.

Use browser animation tooling

Chrome DevTools can pause and scrub view transitions in the Animations panel. While paused, inspect the generated ::view-transition-* pseudo-elements in the Elements panel. Firefox 147 also exposes view-transition pseudo-elements and their animations in its developer tools.

Scrubbing is especially useful for detecting:

  • A snapshot whose box is larger than expected.
  • A different aspect ratio between old and new SVG states.
  • Unexpected root-page animation behind the named SVG.
  • Clipping caused by masks, filters, or large ink overflow.
  • A name applied to more than one rendered component.

Run a practical test matrix

  • Current Chrome or Edge, Safari, and Firefox for same-document behavior.
  • Firefox or another unsupported path to verify cross-document fallback navigation.
  • Reduced Motion enabled at operating-system level.
  • Keyboard-only operation and visible focus.
  • A screen reader check for the updated SVG name and control state.
  • Rapid repeated activation while a previous transition is running.
  • Narrow and wide layouts, zoomed text, and device rotation.
  • Slow network conditions for cross-document transitions.
  • A lower-powered mobile device with the real SVG filters and masks enabled.

Production checklist

  • The UI works completely when the View Transition API is unavailable.
  • The same state-update function powers animated and non-animated paths.
  • Each rendered custom transition name is unique.
  • The outer SVG or stable wrapper is the capture target.
  • The SVG has stable dimensions, viewBox, and aspect-ratio behavior.
  • The transition is not being mistaken for path morphing.
  • The SVG’s accessible name and UI state update with the visual state.
  • Reduced-motion users receive no motion or a deliberately quieter effect.
  • The update callback does not wait for network resources.
  • The effect has been checked with browser animation and performance tools.
  • Cross-document navigation remains usable in Firefox and older browsers.

Frequently asked questions

Can the View Transition API animate an SVG?

Yes. The most reliable production pattern is to give an inline outer svg or its stable wrapper a unique view-transition-name. The browser captures its old and new rendered states, then animates those snapshots.

Does it morph one SVG path into another?

No. A view transition pairs rendered captures; it does not interpolate SVG path commands. Use compatible path interpolation or a morphing library when the geometry itself must transform continuously.

Do same-document SVG view transitions need a framework?

No. Any DOM update can be passed to document.startViewTransition(). A framework router may call it, but a button that changes an SVG attribute or class can use the same API directly.

What happens in an unsupported browser?

For same-document changes, feature detection runs the update immediately. For cross-document transitions, unsupported browsers ignore the CSS opt-in and perform a normal navigation. The final content should be identical.

Why is my transition being skipped?

The most common cause is more than one rendered element using the same view-transition-name. Other causes include a hidden document, a non-rendered target, a viewport change during capture, a failed update callback, or an ineligible cross-document navigation. Inspect the ready and updateCallbackDone promises during development.

Can I use match-element across pages?

No. match-element uses element identity and is therefore limited to same-document transitions. Use a stable explicit custom name on both documents for a cross-document SVG transition.

Should reduced-motion users receive no transition at all?

That depends on the motion. Large scaling, sliding, depth, and spatial movement should generally be removed or replaced. A brief dissolve may be acceptable when it does not create discomfort, but the safest implementation is to bypass same-document capture and disable cross-document animations when reduced motion is requested.

Official references

Sources and further reading

Continue learning
Guide

SVG Animation Not Working? A Production Debugging Playbook

Fix broken SVG animations with a systematic checklist for embedding, CSS, JavaScript, paths, IDs, reduced motion, browser timing, and performance.

Guide

SVG Animation Testing: Visual, Accessibility, and Performance CI

Test SVG animation with Playwright screenshots, reduced-motion checks, axe, ARIA snapshots, runtime assertions, browser coverage, and performance review.