Overview

Astro’s <ViewTransitions /> component intercepts link clicks and replaces full page navigations with animated transitions using the browser’s View Transitions API. The result feels like a single-page app without a client-side router or persistent React tree. Add it to your base layout and every page-to-page navigation gets a cross-fade by default. Astro 5 ships this as a first-class feature with no additional package needed.

Add <ViewTransitions /> to your base layout

Place the component in <head> inside your base layout. Every page that uses that layout inherits SPA-like transitions.

---
import { ViewTransitions } from "astro:transitions";
---
<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>

The default animation is a cross-fade. Astro injects the necessary script to intercept navigations, fetch the next page, and run the transition. No framework runtime is needed.

Use transition:name for element-level animations

Named transitions animate a specific element from its position on the old page to its position on the new page (a shared element transition). Add transition:name to the same logical element on both pages.

<!-- Blog list page -->
<img src={post.cover} transition:name={`cover-${post.slug}`} />
 
<!-- Blog post page -->
<img src={cover} transition:name={`cover-${slug}`} />

The name must be unique per page. Two elements with the same transition:name on the same page cause the transition to break. Use the slug or ID to namespace names.

Control the animation with transition:animate

Override the default animation per element with transition:animate. Astro provides four built-in animations: fade, slide, morph, and none. Import them from astro:transitions.

---
import { fade, slide } from "astro:transitions";
---
<main transition:animate={slide({ duration: "0.3s" })}>
  <slot />
</main>

Use transition:animate="none" to suppress the animation on an element while other elements still transition.

Persist elements across navigations with transition:persist

transition:persist keeps an element mounted in the DOM across page navigations instead of unmounting and remounting it. Use it for media players, sticky headers with client state, or an island that should not lose its state when the user navigates.

<AudioPlayer client:load transition:persist />

Persisted elements keep their DOM state but do not re-run onMount or component constructors. For React islands, hooks and state survive the navigation. This is the right pattern for a music player that should keep playing while the user browses.

Handle fallback for browsers without View Transitions support

Safari added View Transitions support in 2024; Firefox added it in 2025. For older browsers, Astro falls back to a normal full-page navigation. There is no flash of unstyled content because Astro server-renders each page. The site works correctly without transitions; they are a progressive enhancement.

To disable the fallback behavior and use a custom JS animation for unsupported browsers, pass fallback="animate" to <ViewTransitions />. The fallback="none" value disables transitions on unsupported browsers entirely.

<ViewTransitions fallback="animate" />

Avoid View Transitions on pages with heavy client-side state

View Transitions work best when pages are mostly static. If a page has a React island with significant client state (a multi-step form, a shopping cart), a transition into that page rehydrates the island fresh. That is usually fine. But a transition away from that page will unmount it, discarding state, unless you use transition:persist.

For pages where navigation should not be intercepted (an OAuth redirect, a payment confirmation), add data-astro-reload to the link element:

<a href="/checkout/confirm" data-astro-reload>Confirm Payment</a>

This forces a full page load for that link only.