Capítulo 16 de 24

Chapter 16: View Transitions

Core Idea

This example wires the browser's native View Transitions API into route navigation, so switching between pages (e.g. Home and Posts) animates as a transition instead of an instant swap. TanStack Router exposes this per-Link/navigate via a viewTransition flag, or globally via defaultViewTransition on the router.

Setup

  • Routing style: file-based (createFileRoute, routes generated into routeTree.gen.ts)
  • Key dependencies: @tanstack/react-router, @tanstack/router-plugin (drives file-based route generation), zod, @tanstack/react-router-devtools
  • Structure: src/main.tsx creates the router from the generated routeTree; src/routes/__root.tsx defines the root layout and nav links; src/routes/posts.$postId.tsx is a file-based dynamic route for an individual post, loaded via a loader.

Code Example

// src/routes/__root.tsx
<Link
  to="/"
  activeProps={{ className: 'font-bold' }}
  activeOptions={{ exact: true }}
  viewTransition
>
  Home
</Link>{' '}
<Link
  to="/posts"
  activeProps={{ className: 'font-bold' }}
  viewTransition
>
  Posts
</Link>

The router also supports enabling this globally instead of per-link, including deriving transition "types" from the navigation direction:

// src/main.tsx (commented alternative shown in the example)
// defaultViewTransition: true
// OR
// defaultViewTransition: {
//   types: ({ fromLocation, toLocation }) => {
//     let direction = 'none'
//     if (fromLocation) {
//       const fromIndex = fromLocation.state.__TSR_index
//       const toIndex = toLocation.state.__TSR_index
//       direction = fromIndex > toIndex ? 'right' : 'left'
//     }
//     return [`slide-${direction}`]
//   },
// },

The destination route itself is a plain file-based route with a loader and error/not-found components:

// src/routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params: { postId } }) => fetchPost(postId),
  errorComponent: PostErrorComponent,
  notFoundComponent: () => <p>Post not found</p>,
  component: PostComponent,
})
  • What it demonstrates: the viewTransition prop on Link (or navigate) opts a specific navigation into document.startViewTransition, while defaultViewTransition on createRouter can apply it to every navigation and optionally compute per-navigation transition types (e.g. slide direction) from fromLocation/toLocation history state.

Key Takeaways

  1. viewTransition on Link/navigate is opt-in per navigation; set defaultViewTransition: true on the router instead if you want it everywhere without annotating every link.
  2. defaultViewTransition.types as a function receiving { fromLocation, toLocation } lets you compute CSS view-transition types (e.g. slide-left vs slide-right) from navigation direction, using the router's internal __TSR_index history state to detect back/forward vs forward navigation.
  3. This feature depends on browser support for the View Transitions API; it degrades to a normal instant navigation where unsupported, so no fallback branching is required in route code.
  4. Pair viewTransition with route-scoped CSS (view-transition-name) on the elements you want individually animated; the router only triggers the transition, the CSS still defines what animates.

Connects To

  • Scroll Restoration (ch015): both features affect the perceived smoothness of a navigation; combining scrollRestoration: true with view transitions avoids a jarring scroll-jump mid-animation.
  • Location Masking (ch013): masked modal routes are a natural fit for view transitions, animating an overlay in over content that visually stays in place.