View Transitions API: Native Page Animations Without a JavaScript Framework

Navigation transitions on the web have historically been a choice between two bad options. You either accept the browser’s default — an instant page swap that gives users no spatial context — or you build a JavaScript animation layer that intercepts routing, manages a virtual DOM of transitioning elements, and adds hundreds of milliseconds of overhead. Neither option is great. The first feels cheap. The second is expensive to build and maintain.

The View Transitions API changes this. It’s a native browser API that captures a snapshot of the current page state, lets you apply CSS animations to the transition between old and new states, and handles all the compositing for you. No JavaScript animation framework. No virtual DOM. Just a single function call and a few CSS rules.

Same-Document Transitions: The Building Block

The simplest form of view transition happens within a single page — think of a list view where clicking an item expands it to a detail panel. You call document.startViewTransition(), pass it a callback that updates the DOM, and the browser handles the rest:

// Capture the current state, update DOM, animate the difference
function showDetail(cardElement) {
  document.startViewTransition(() => {
    // Your normal DOM update — swap classes, move content, etc.
    cardElement.classList.add('expanded');
    detailPanel.classList.remove('hidden');
  });
}

When this runs, the browser takes a snapshot of the page before and after your callback executes, then animates between them using the CSS you’ve defined. The default animation is a cross-fade, which already looks polished. But the real power comes from naming elements and customizing the animation.

Naming Elements for Smooth Morphs

The view-transition-name CSS property is what makes the API special. When two elements — one in the old state and one in the new state — share the same view-transition-name, the browser automatically animates a smooth morph between their positions and sizes:

/* In the list view, each card's image gets a unique name */
.product-card .product-image {
  view-transition-name: var(--image-name);
}

/* In the detail view, the hero image gets the same name */
.product-detail .hero-image {
  view-transition-name: var(--image-name);
}

/* The shared element morphs from list position to detail position */
::view-transition-old(var(--image-name)) {
  animation: 0.4s ease both fade-out;
}

::view-transition-new(var(--image-name)) {
  animation: 0.4s ease both fade-in;
}

This is the same “shared element transition” pattern that native mobile frameworks have offered for years. The browser figures out the position and size delta between the old and new snapshots, and animates a smooth morph. If the element moves from the top-left to the center, it glides there. If it grows from a 200px thumbnail to a full-width hero, it scales up smoothly.

Cross-Document Transitions: Full Page Navigation

While same-document transitions are great for SPAs, the real game-changer is cross-document transitions. These let you animate navigations between completely separate page loads — a standard multi-page application, no client-side router required.

To enable cross-document transitions, you use the @view-transition at-rule:

@view-transition {
  navigation: auto;
}

That’s it. Once this rule is present on both the outgoing and incoming pages, the browser captures snapshots during navigation and applies view transition animations. Named elements on both pages will morph between their positions. A product image on a listing page can smoothly expand into the hero position on the product detail page, even though those are entirely separate HTML documents.

Cross-document view transitions shipped in Chrome 126 and Safari 18.2. Firefox support is still in progress. This means you can use them today with progressive enhancement — browsers that support it get the smooth transition, others get the normal instant navigation.

Styling Groups of Transitions

As you start naming more elements, you’ll want to apply different animations to different groups. A sidebar should slide; a card should fade. The view-transition-class property lets you tag elements and then style their transition pseudo-elements together. Available since Chrome 125 and Safari 18.2, this property works like a CSS class for transition pseudo-elements:

/* Tag elements with a transition class */
.sidebar {
  view-transition-name: site-sidebar;
  view-transition-class: slide-panel;
}

.modal-overlay {
  view-transition-name: modal-overlay;
  view-transition-class: slide-panel;
}

/* Apply shared animation to the whole group */
::view-transition-group(*.slide-panel) {
  animation-duration: 0.3s;
  animation-timing-function: ease-out;
}

Element-Scoped Transitions: Containing the Animation

By default, view transitions capture the entire page. For large pages, this can mean capturing and compositing a lot of content, which may affect performance. Element-scoped transitions, which shipped in Chrome 147, let you limit the snapshot to a specific container element instead of the full document:

/* Limit transition to a specific container */
.data-grid {
  view-transition-name: grid-container;
}

/* Only elements inside this container participate in the transition */
::view-transition-group(grid-container) {
  position: absolute;
  overflow: hidden;
}

This is particularly useful for dashboards and data-heavy applications where only a section of the page changes. Instead of snapshotting the entire viewport, the browser only captures and animates the named container, reducing compositing overhead.

Browser Support and Progressive Enhancement

Here’s the current support matrix for the key features:

FeatureChromeFirefoxSafari
Same-document transitions111+144+18+
Cross-document transitions126+Not yet18.2+
view-transition-class125+144+18.2+
Element-scoped transitions147+Not yetNot yet

Same-document transitions have reached Baseline status — they’re available across all major browsers. Cross-document transitions work in Chromium and Safari but are still missing from Firefox. Element-scoped transitions are Chrome-only for now.

Feature Detection and Fallbacks

Feature detection is straightforward. Check for the API before calling it, and your app degrades gracefully:

function navigateWithTransition(updateCallback) {
  if (document.startViewTransition) {
    document.startViewTransition(() => {
      updateCallback();
    });
  } else {
    // Fallback: just update the DOM without animation
    updateCallback();
  }
}

// For cross-document transitions in CSS, feature detection is automatic:
// The @view-transition rule is ignored by browsers that don't support it,
// so navigation simply falls back to the default instant swap.

Performance Considerations

View transitions use the browser’s compositor, which means the animations run on the GPU and don’t block the main thread. But the snapshot capture itself isn’t free. For complex pages with hundreds of DOM elements, capturing the old and new states takes time. Here are practical guidelines:

  • Name only what you need to animate. Every view-transition-name creates a separate snapshot. A page with 50 named elements will capture 50 snapshots. Name the few elements that need to morph.
  • Avoid animating during fast scroll. Transitions during scroll jank the experience. Consider disabling transitions for programmatic scroll navigation.
  • Keep animations short. 200-400ms is the sweet spot. Longer than 500ms feels sluggish, especially on repeated navigations.
  • Test on low-end devices. Snapshot capture is CPU-intensive. A transition that looks great on a developer laptop may stutter on a budget phone.

Integration with Frameworks

Most modern frameworks have integrated or are integrating the View Transitions API. Astro, SvelteKit, and Next.js all offer built-in support. In SvelteKit, for example, cross-document transitions work out of the box — you just need to name elements and the framework handles the rest:

<!-- List.svelte -->
<a href="/products/{product.id}" data-sveltekit-view-transition-name="product-{product.id}">
  <img src={product.image} alt={product.name} />
</a>

<!-- Detail.svelte -->
<div data-sveltekit-view-transition-name="product-{product.id}">
  <img src={product.image} alt={product.name} class="hero-image" />
</div>

Wrapping Up

The View Transitions API brings a capability to the web platform that was previously locked behind heavy JavaScript frameworks. Same-document transitions are production-ready across all browsers. Cross-document transitions work in Chrome and Safari, making them viable for a large share of real users today. Start with a simple cross-fade, name a few key elements, and you’ll get an immediate, noticeable improvement in how your app feels.

The API’s real value isn’t just visual polish — it’s spatial continuity. When users see elements morph between positions instead of popping in and out of existence, they maintain context. That context is what separates a collection of pages from a cohesive application. With the View Transitions API, that experience is now a few CSS rules away.

Leave a Reply

Your email address will not be published. Required fields are marked *