View transitions are animated transitions between different website views, preserving visual continuity as visitors navigate your site.
What are View Transitions?
Astro’s view transitions support is powered by the View Transitions browser API and includes:
- Built-in animations (
fade, slide, none)
- Forwards and backwards navigation animations
- Custom animation support
- Persistent elements across navigation
- Client-side routing with the
<ClientRouter /> component
- Fallback support for unsupported browsers
- Automatic
prefers-reduced-motion support
Enabling View Transitions
Import and add the <ClientRouter /> component to your common <head> or layout:
---
import { ClientRouter } from "astro:transitions";
---
<head>
<title>My Site</title>
<ClientRouter />
</head>
This enables default page animations and client-side routing site-wide.
Transition Directives
Control transition behavior with transition:* directives:
transition:name
Identify matching elements across pages:
<!-- old-page.astro -->
<aside transition:name="hero">
<!-- new-page.astro -->
<aside transition:name="hero">
transition:animate
Specify animation type:
<header transition:animate="slide">
<h1>My Header</h1>
</header>
Built-in animations:
fade (default) - Crossfade animation
slide - Slides old content out, new content in
initial - Use browser’s default
none - Disable animations
transition:persist
Persist elements across navigation:
<video controls autoplay transition:persist>
<source src="video.mp4" type="video/mp4" />
</video>
Persist framework components with state:
<Counter client:load transition:persist initialCount={5} />
Shorthand with name:
<video transition:persist="media-player">
Custom Animations
Customizing Built-in Animations
---
import { fade } from "astro:transitions";
---
<header transition:animate={fade({ duration: "0.4s" })}>
Creating Custom Animations
-
Define keyframes in a global style tag:
<style is:global>
@keyframes bump {
0% {
opacity: 0;
transform: scale(1) translateX(200px);
}
50% {
opacity: 0.5;
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1) translateX(0);
}
}
</style>
-
Define animation behavior in frontmatter:
---
const anim = {
old: {
name: "bump",
duration: "0.5s",
easing: "ease-in",
direction: "reverse",
},
new: {
name: "bump",
duration: "0.5s",
easing: "ease-in-out",
},
};
const customTransition = {
forwards: anim,
backwards: anim,
};
---
<header transition:animate={customTransition}>
Router Control
Preventing Client-side Navigation
Opt out of routing on specific links:
<a href="/articles/emperor-penguins" data-astro-reload>
Trigger Navigation Programmatically
Use navigate() from astro:transitions/client:
<script>
import { navigate } from "astro:transitions/client";
document.querySelector("select").onchange = (event) => {
navigate(event.target.value);
};
</script>
<select>
<option value="/play">Play</option>
<option value="/blog">Blog</option>
<option value="/about">About</option>
</select>
Browser History Control
Control history entries with data-astro-history:
<a href="/main" data-astro-history="replace">
Options:
push - Add new entry (default)
replace - Replace current entry
auto - Attempt push, fall back if needed
Forms support view transitions for both GET and POST:
<form
action="/contact"
method="POST"
enctype="application/x-www-form-urlencoded"
>
Opt out with data-astro-reload:
<form action="/contact" data-astro-reload>
Lifecycle Events
Listen to navigation events to customize behavior:
astro:before-preparation
Fires before loading starts:
<script is:inline>
document.addEventListener("astro:before-preparation", (event) => {
const originalLoader = event.loader;
event.loader = async function () {
const { startSpinner } = await import("./spinner.js");
const stop = startSpinner();
await originalLoader();
stop();
};
});
</script>
astro:after-preparation
Fires after content is loaded:
<script is:inline>
document.addEventListener("astro:before-preparation", () => {
document.querySelector("#loading").classList.add("show");
});
document.addEventListener("astro:after-preparation", () => {
document.querySelector("#loading").classList.remove("show");
});
</script>
astro:before-swap
Fires before the new document replaces the old:
<script>
document.addEventListener("astro:before-swap", (event) => {
event.newDocument.documentElement.dataset.theme =
localStorage.getItem("darkMode") ? "dark" : "light";
});
</script>
astro:after-swap
Fires after page swap:
document.addEventListener("astro:after-swap", () => {
window.scrollTo({ left: 0, top: 0, behavior: "instant" });
});
astro:page-load
Fires when navigation completes:
<script>
document.addEventListener("astro:page-load", () => {
// Runs on every navigation
setupEventListeners();
});
</script>
Script Behavior
Script Re-execution
Bundled module scripts execute once. Inline scripts may re-execute.
Wrap scripts in event listeners:
document.addEventListener("astro:page-load", () => {
document.querySelector(".hamburger").addEventListener("click", () => {
document.querySelector(".nav-links").classList.toggle("expanded");
});
});
Force Re-run with data-astro-rerun
<script is:inline data-astro-rerun>
// This runs on every navigation
</script>
Preserve Global State
<script is:inline>
if (!window.SomeGlobal) {
window.SomeGlobal = {};
}
</script>
Fallback Control
Configure fallback for unsupported browsers:
<ClientRouter fallback="swap" />
Options:
animate (default) - Simulate transitions
swap - Immediate page replacement
none - Full page navigation
Accessibility
Route Announcement
The <ClientRouter /> automatically announces route changes for screen readers using the page title, first <h1>, or pathname.
Always include a <title> for accessibility.
prefers-reduced-motion
All animations are automatically disabled when prefers-reduced-motion is detected.
Client-side Navigation Process
When using <ClientRouter />:
- Visitor triggers navigation (click, back/forward button)
- Router fetches the next page
- Router adds
data-astro-transition attribute
- Browser’s
startViewTransition is called
- Router performs swap:
- Head elements are updated
- Body is replaced
- Persistent elements are moved
- Scroll position is restored
- New stylesheets load
- New scripts execute
astro:page-load event fires
Best Practices
Theme Persistence
<script is:inline>
function applyTheme() {
localStorage.theme === "dark"
? document.documentElement.classList.add("dark")
: document.documentElement.classList.remove("dark");
}
document.addEventListener("astro:after-swap", applyTheme);
applyTheme();
</script>
Loading Indicators
<script>
document.addEventListener("astro:before-preparation", () => {
document.getElementById("loading").style.display = "block";
});
document.addEventListener("astro:page-load", () => {
document.getElementById("loading").style.display = "none";
});
</script>
Sanitize URLs before passing to navigate():
<script>
import { navigate } from 'astro:transitions/client';
const params = new URLSearchParams(window.location.search);
const redirect = params.get('redirect');
const allowedPaths = ['/home', '/about', '/contact'];
if (allowedPaths.includes(redirect)) {
navigate(redirect);
}
</script>