notes
Dec 14, 2024 · 2 min read

A Weekend Inside View Transitions

I wanted exactly one thing from astro:transitions: a soft cross-fade between the home page and an article page, so navigating didn’t feel like a hard reload even though, technically, nothing was being kept alive across the navigation. I did not expect this to take a weekend.

Turning it on is the easy part

Dropping <ClientRouter /> into the layout took about thirty seconds and produced a page transition immediately. It also produced a transition on every single element with a shared id, most of which I hadn’t intended — the header wordmark slid instead of cross-fading, and a stray duplicate heading id caused two unrelated elements to morph into each other for one very strange frame.

---
import { ClientRouter } from 'astro:transitions';
---
<ClientRouter />

Scoping it down

The fix was to be deliberate about which elements actually get a transition:name, instead of letting the router infer shared elements from matching ids:

  • The article title gets an explicit transition:name so it feels continuous between the card and the article page.
  • Everything else gets transition:animate="fade" explicitly, or nothing at all, so it doesn’t try to be clever with elements that just happen to share markup.
  • The header and footer opt out entirely with transition:persist, since they’re identical across every page and re-animating them is pure waste.

Respecting the setting that matters most

None of this is worth much if it ignores prefers-reduced-motion. Astro’s router respects it by default for its own transitions, but I’d layered a few CSS transitions of my own on top, and those needed an explicit media query:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

A weekend for one cross-fade sounds like a bad trade until I remember what it replaced: a full white flash between every page, which is the kind of thing readers feel even when they can’t articulate why a site feels slower than it measures.