---
title: "Astro: View Transitions"
slug: "astro-view-transitions"
category: "frontend"
tags: ["frontend", "astro", "view-transitions", "navigation", "animation"]
status: "stable"
last_updated: 2026-08-14
summary: "Add SPA-feel navigation to a static Astro site with the ClientRouter component, named transitions, and persistent elements."
related: ["[[frontend/astro]]", "[[frontend/astro-islands]]", "[[frontend/astro-performance]]", "[[comparisons/nextjs-vs-astro]]", "[[seo/page-speed]]", "[[frontend/astro-ssr]]"]
---

## Overview

Astro's `<ClientRouter />` 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.0 renamed the component from `<ViewTransitions />` to `<ClientRouter />` (functionality unchanged); Astro 6.0 removed the `<ViewTransitions />` alias entirely, so `<ClientRouter />` is now the only name that works.

## Add `<ClientRouter />` to your base layout

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

```astro
---
import { ClientRouter } from "astro:transitions";
---
<html>
  <head>
    <ClientRouter />
  </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. If a project still imports `ViewTransitions` from `astro:transitions`, the build fails on Astro 6; rename the import and the JSX tag to `ClientRouter`.

## 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.

```astro
<!-- 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`.

```astro
---
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.

```astro
<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 `<ClientRouter />`. The `fallback="none"` value disables transitions on unsupported browsers entirely.

```astro
<ClientRouter 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:

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

This forces a full page load for that link only.

## Related

- [[frontend/astro]]
- [[frontend/astro-islands]]
- [[frontend/astro-performance]]
- [[comparisons/nextjs-vs-astro]]
- [[seo/page-speed]]
- [[frontend/astro-ssr]]
