Skip to main content
View Transitions provide smooth, animated transitions between pages in your Astro site, creating a single-page application (SPA) experience while maintaining the benefits of multi-page architecture.

Getting Started

Enable view transitions by adding the <ViewTransitions /> component to your layout’s <head>:
src/layouts/Layout.astro
---
import { ViewTransitions } from 'astro:transitions';
---

<html>
  <head>
    <meta charset="utf-8" />
    <title>My Site</title>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>
With just this one component, Astro handles all the complexity of page transitions, including routing, animation, and state management.

How It Works

When a user clicks a link:
1

Intercept Navigation

Astro intercepts the navigation and prevents the default page load
2

Fetch New Page

The new page is fetched in the background
3

Animate Transition

Elements fade out, move, or morph based on their transition names
4

Update Content

The DOM is updated with the new page content
5

Complete

Elements animate in and the transition completes

Basic Transitions

By default, all pages get a cross-fade transition. Customize transitions using the transition:* directives:
src/pages/index.astro
---
import { fade, slide } from 'astro:transitions';
---

<html>
  <body>
    <header transition:persist>
      <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
      </nav>
    </header>

    <main transition:animate={slide({ duration: '0.3s' })}>
      <h1>Welcome</h1>
    </main>
  </body>
</html>

Transition Directives

transition:name

Persist or morph elements across pages by giving them the same name:
src/pages/products/[id].astro
---
const { id } = Astro.params;
const product = await getProduct(id);
---

<img 
  src={product.image} 
  alt={product.name}
  transition:name={`product-${id}`}
/>

<h1 transition:name={`title-${id}`}>
  {product.name}
</h1>
Elements with the same transition:name will smoothly morph between pages:
src/pages/products/index.astro
---
const products = await getProducts();
---

<div class="grid">
  {products.map(product => (
    <a href={`/products/${product.id}`}>
      <img
        src={product.image}
        alt={product.name}
        transition:name={`product-${product.id}`}
      />
      <h2 transition:name={`title-${product.id}`}>
        {product.name}
      </h2>
    </a>
  ))}
</div>
When navigating from the grid to a product page, the image and title smoothly animate from their list position to their detail position.

transition:persist

Keep elements in the DOM across page transitions:
<video 
  src="/video.mp4" 
  controls
  transition:persist
/>

<audio 
  src="/music.mp3"
  controls
  transition:persist
/>
Persisted elements maintain their state (video position, form inputs, etc.).

transition:animate

Customize animation styles:
---
import { fade, slide } from 'astro:transitions';
---

<!-- Fade animation -->
<div transition:animate={fade({ duration: '0.5s' })}>
  Content
</div>

<!-- Slide animation -->
<div transition:animate={slide({ duration: '0.3s' })}>
  Slides in from the right
</div>

<!-- No animation -->
<div transition:animate="none">
  Instant swap
</div>

Built-in Animations

---
import { fade } from 'astro:transitions';
---

<div transition:animate={fade({ duration: '0.4s' })}>
  Fades in and out
</div>

Directional Animations

Create different animations for forward and backward navigation:
---
import { slide } from 'astro:transitions';
---

<main transition:animate={slide({ duration: '0.3s' })}>
  <h1>Content</h1>
</main>
The slide animation automatically:
  • Slides left when going forward
  • Slides right when going back

Lifecycle Events

Listen to transition events to run code during transitions:
<script>
  document.addEventListener('astro:before-preparation', (event) => {
    console.log('About to prepare for transition');
  });

  document.addEventListener('astro:after-preparation', (event) => {
    console.log('Preparation complete');
  });

  document.addEventListener('astro:before-swap', (event) => {
    console.log('About to swap content');
  });

  document.addEventListener('astro:after-swap', (event) => {
    console.log('Content swapped');
  });

  document.addEventListener('astro:page-load', (event) => {
    console.log('Page load complete');
  });
</script>

Event Reference

astro:before-preparation
Event
Fires before fetching the new page
astro:after-preparation
Event
Fires after fetching and parsing the new page
astro:before-swap
Event
Fires before updating the DOM
astro:after-swap
Event
Fires after updating the DOM
astro:page-load
Event
Fires when the page is fully loaded and interactive

Preventing Transitions

Disable transitions for specific links:
<!-- Regular link with transition -->
<a href="/page">Transitions enabled</a>

<!-- Link without transition -->
<a href="/page" data-astro-reload>No transition</a>

<!-- External links automatically skip transitions -->
<a href="https://example.com">External link</a>

Fallback Behavior

View Transitions gracefully degrade in browsers that don’t support the View Transitions API:
  • Modern browsers: Smooth animated transitions
  • Older browsers: Standard page navigation
  • JavaScript disabled: Normal links work as expected
The View Transitions API is supported in Chrome, Edge, and other Chromium browsers. Safari and Firefox users get standard navigation.

Preserving State

Form Inputs

Persist form state across navigation:
<form transition:persist="search-form">
  <input 
    type="search" 
    name="q" 
    placeholder="Search..."
  />
  <button type="submit">Search</button>
</form>

Media Playback

Keep videos and audio playing:
<video 
  src="/background.mp4"
  autoplay
  loop
  muted
  transition:persist
/>

Third-Party Scripts

Persist widgets that shouldn’t reinitialize:
<div id="chat-widget" transition:persist>
  <!-- Chat widget loads once and persists -->
</div>

Advanced Patterns

Loading Indicators

Show loading state during transitions:
src/components/LoadingBar.astro
<div id="loading-bar" class="loading-bar"></div>

<style>
  .loading-bar {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 3px;
    background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
    transition: width 0.3s ease;
    z-index: 9999;
  }

  .loading-bar.active {
    width: 100%;
  }
</style>

<script>
  const bar = document.getElementById('loading-bar');

  document.addEventListener('astro:before-preparation', () => {
    bar?.classList.add('active');
  });

  document.addEventListener('astro:after-swap', () => {
    setTimeout(() => {
      bar?.classList.remove('active');
    }, 300);
  });
</script>

Scroll Restoration

Customize scroll behavior:
<script>
  // Scroll to top on navigation
  document.addEventListener('astro:after-swap', () => {
    window.scrollTo({ top: 0, behavior: 'smooth' });
  });

  // Preserve scroll position
  let scrollPos = 0;

  document.addEventListener('astro:before-preparation', () => {
    scrollPos = window.scrollY;
  });

  document.addEventListener('astro:after-swap', () => {
    window.scrollTo({ top: scrollPos });
  });
</script>

Page-Specific Transitions

Different animations for different pages:
src/pages/index.astro
---
import { fade } from 'astro:transitions';
---

<main transition:animate={fade({ duration: '0.5s' })}>
  <h1>Home</h1>
</main>
src/pages/gallery.astro
---
import { slide } from 'astro:transitions';
---

<main transition:animate={slide({ duration: '0.3s' })}>
  <h1>Gallery</h1>
</main>

View Transition Scope

Create isolated transition contexts:
---
import { createAnimationScope } from 'astro:transitions';

const scope = createAnimationScope('my-section');
---

<section data-transition-scope={scope}>
  <img 
    src="/image.jpg"
    transition:name="hero"
    transition:scope={scope}
  />
</section>

Performance Tips

1

Use transition:name sparingly

Only add transition:name to elements that truly need morphing. Too many can impact performance.
2

Keep animations short

Transitions under 300ms feel snappy. Avoid durations over 500ms.
3

Optimize images

Use optimized images for smoother transitions, especially for morphing elements.
4

Test on slow connections

View transitions wait for the new page to load. Test on throttled connections.

Accessibility

View Transitions respect user preferences:
/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
  :root {
    --animation-duration: 0.01ms !important;
  }
}
Astro automatically:
  • Announces page changes to screen readers
  • Updates the document title
  • Manages focus appropriately
  • Respects prefers-reduced-motion

Troubleshooting

  • Ensure <ViewTransitions /> is in your layout’s <head>
  • Check that you’re using the same layout across pages
  • Verify the View Transitions API is supported in your browser
  • Use transition:persist for elements that should maintain state
  • Ensure CSS is loaded before the transition starts
  • Check for conflicting animations
  • Use the astro:page-load event instead of DOMContentLoaded
  • Wrap script logic in event listeners that re-run on navigation

Islands

Client-side interactivity

Routing

Page routing and navigation

Layouts

Shared page layouts

Performance

Optimization techniques

Build docs developers (and LLMs) love