webdev.complete
🌟 View Transitions, PWA, Edge
The Modern Frontier
Lesson 115 of 117
20 min

View Transitions (Cross-Doc)

Browser-native page morphs. Same-doc and across documents.

For years, native apps had silky animated transitions between screens, and the web had... a hard cut. The View Transitions API finally gives browsers a single primitive for animating DOM changes, including across full page navigations. Two lines of code can turn a jarring swap into a Hollywood-grade morph. And as of 2025, all major browsers support same-document transitions, with cross-document close behind.

The core idea: snapshot, swap, animate

Under the hood the browser does three things when you start a view transition:

  1. Snapshot the current page as a still image (the old state).
  2. Run your DOM mutation callback. Snapshot the result (the new state).
  3. Animate from old to new using CSS pseudo-elements that you can target and customize.

It is essentially a built-in version of the FLIP technique animation nerds have been hand-rolling for years. Except now it works for anything, including stuff that would normally be impossible to animate (like an element morphing into a completely different element).

The minimum-viable example

js
function toggleTheme() {
  // 1. Wrap your DOM mutation in startViewTransition.
  if (!document.startViewTransition) {
    // Fallback for old browsers
    document.body.classList.toggle("dark");
    return;
  }

  document.startViewTransition(() => {
    document.body.classList.toggle("dark");
  });
}

That is it. The browser will smoothly cross-fade the entire page between light and dark. No JS animation library. No CSS keyframes you wrote yourself.

Default = cross-fade root
Without any extra CSS, the browser animates a root-level cross-fade. That is rarely what you actually want. The good stuff happens once you name specific elements.

Named transitions for shared elements

Give two elements (one on the old page, one on the new) the same view-transition-name, and the browser will animate from one's size and position to the other's. This is the "Apple Music album art zoom" pattern, available with zero JavaScript animation code.

css
/* Old page: the small thumbnail */
.card-thumb {
  view-transition-name: hero-image;
}

/* New page: the big detail hero */
.detail-hero {
  view-transition-name: hero-image;
}

/* Optional: customize the morph itself */
::view-transition-old(hero-image),
::view-transition-new(hero-image) {
  animation-duration: 0.5s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

Each named element gets two pseudo-elements during the transition: ::view-transition-old(name) and ::view-transition-new(name). You style or animate those with regular CSS. The browser handles the position interpolation for you.

Names must be unique at any given moment
Two visible elements cannot share a view-transition-name on the same page or the transition will skip. If you have a grid of cards, only one card at a time can have the shared name (typically the one you clicked).

Cross-document transitions

Same-document transitions are great for SPAs. But the web is built on multi-page navigation, and the API now extends there too. Two CSS rules opt you in:

styles.css
/* Opt both pages into cross-document transitions. */
@view-transition {
  navigation: auto;
}

/* Shared element on both pages. */
.product-image {
  view-transition-name: product-hero;
  /* important: each instance must have a unique name across the doc */
}

With this in place, clicking a link to another page on the same origin will trigger a real page navigation that the browser animates. No SPA framework required.

Speculation Rules: pre-render the next page

Cross-document transitions look magical, but if the next page takes 2 seconds to download, the animation either stalls or feels disconnected from the navigation. Speculation Rules let you tell the browser to pre-render likely next pages so they are ready the instant the user clicks.

html
<script type="speculationrules">
{
  "prerender": [
    {
      "where": { "href_matches": "/products/*" },
      "eagerness": "moderate"
    }
  ]
}
</script>

Combined with cross-document view transitions, the result feels instant. Click a product card and you are already on the detail page, with the hero image morphing into place.

Try it: list-to-detail morph

This sandbox shows a same-document version of the pattern: click a card, it animates into the detail view. Click back, it animates back. All three cards share a thumbnail; only the active one gets the shared name, so we set it dynamically.

<!doctype html>
<html>
<head>
  <link rel="stylesheet" href="./styles.css" />
</head>
<body>
  <main id="app">
    <section id="list" class="list">
      <article class="card" data-id="1" style="--accent:#ff6f61">
        <div class="thumb"></div>
        <h3>Sunset Volcano</h3>
      </article>
      <article class="card" data-id="2" style="--accent:#6bd1ff">
        <div class="thumb"></div>
        <h3>Glacier Bay</h3>
      </article>
      <article class="card" data-id="3" style="--accent:#9b6bff">
        <div class="thumb"></div>
        <h3>Lavender Fields</h3>
      </article>
    </section>

    <section id="detail" hidden>
      <button id="back">&larr; Back</button>
      <div class="hero"></div>
      <h2 id="title"></h2>
      <p>Tap back to morph it home.</p>
    </section>
  </main>
  <script src="./index.js"></script>
</body>
</html>
Check support before you commit
Always test if (document.startViewTransition) before calling it. Older browsers will just run your callback synchronously and the page works fine, just without animation.

Quick quiz

Quiz1 / 3

What does document.startViewTransition(callback) actually do?

Recap

  • document.startViewTransition(cb) snapshots the page, runs your DOM mutation, and animates between states.
  • Matching view-transition-name on old and new elements triggers a shared-element morph.
  • Style the ::view-transition-old(name) and ::view-transition-new(name) pseudo-elements to customize timing and effects.
  • Cross-document mode is enabled with @view-transition { navigation: auto; }.
  • Pair cross-document transitions with Speculation Rules for instant navigation feel.
Built with Next.js, Tailwind & Sandpack.
Learn. Build. Ship.