View transitions
How Astro's ViewTransitions component provides smooth page-to-page navigation and why dark mode state has to be re-applied on each swap.
The garden uses Astro’s built-in ViewTransitions component for smooth client-side navigation between pages — without a full browser reload.
How it works
<ViewTransitions /> is included in BaseLayout.astro inside <head>. It intercepts link clicks and replaces page content via the browser’s View Transitions API (or falls back to a regular navigation on unsupported browsers).
import { ViewTransitions } from 'astro:transitions';
// ...
<ViewTransitions />
That’s the entire configuration — Astro handles the animation and swap mechanism.
The dark mode problem
View transitions swap the <html> element’s content but do not re-run <head> scripts on each navigation. This means the data-theme attribute set by the no-flash script in <head> is wiped on every page transition.
To fix this, a second inline script listens for Astro’s astro:after-swap event and re-applies the theme:
document.addEventListener('astro:after-swap', () => {
const stored = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = stored || (prefersDark ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
});
astro:after-swap fires after the new page DOM is in place but before it’s painted. Re-setting data-theme here prevents a flash of the wrong theme on navigation.
Component re-initialization
Any JavaScript that runs on page load also needs to run on astro:after-swap. In Header.astro, the dropdown setup functions are called in both places:
document.addEventListener('DOMContentLoaded', setupGardenDropdown);
document.addEventListener('astro:after-swap', setupGardenDropdown);
Without this, the dropdowns would stop working after the first navigation.
Performance benefit
Transitions make navigation feel instant. The critical path is: intercept click → fetch new page → swap DOM → paint. There’s no full page reload, no FOUC, no layout flash. Combined with Astro’s static output (no server), pages load from CDN cache in milliseconds.
When transitions don’t fire
- Hard refreshes (
Ctrl+R) - External links (open in new tab or navigate away)
- Links with
data-astro-reloadattribute (forces full reload)
The no-flash script in <head> handles these cases: it runs on every hard load and sets the theme from localStorage before painting.