When navigating between HTML pages on traditional multi-page applications (MPAs), the browser reloads the page, causing an abrupt white flash or jump. For years, achieving fluid transitions meant building a Single Page Application (SPA) with React, Vue, or heavy client-side routing libraries.
That is no longer required. Modern browsers now support Native Cross-Document View Transitions, allowing smooth animations between separate, real HTML files using pure CSS.
In this tutorial, we will turn a multi-page luxury automotive site (Orion Motors) into an app-like experience with zero custom JavaScript.
1. The Core Rule: @view-transition
To allow separate HTML documents to participate in a cross-document view transition, both the outgoing and incoming pages must opt in.
If all your HTML pages share a single stylesheet (e.g., styles.css), you only need to write this single rule:
@view-transition { navigation: auto; } Note: This rule enables native cross-document transitions for eligible, same-origin navigations.
2. Customizing Global Page Transitions
When a view transition occurs, the browser creates temporary pseudo-elements representing the outgoing state (::view-transition-old(root)) and the incoming state (::view-transition-new(root)).
We can apply custom keyframe animations to these pseudo-elements to control our exit and entry effects:
/* Target the outgoing page */ ::view-transition-old(root) { animation: 0.36s ease-in pageOut; } /* Target the incoming page */ ::view-transition-new(root) { animation: 0.48s cubic-bezier(0.22, 1, 0.36, 1) pageIn; } /* Keyframe Animations */ @keyframes pageOut { to { opacity: 0; filter: blur(4px); transform: scale(0.98); } } @keyframes pageIn { from { opacity: 0; filter: blur(4px); transform: translateY(12px); } to { opacity: 1; filter: blur(0); transform: translateY(0); } } 3. Shared Element Transitions with view-transition-name
Animating whole pages is great, but modern app UI relies on shared elements—like an image on a product card morphing smoothly into the main hero image of a detail page.
To achieve this:
- Give the element on Page A and the corresponding element on Page B the exact same
view-transition-name. - Ensure each name is unique per rendered page.
/* Orion V12 Car Model */ .car-v12 { view-transition-name: orion-v12; } /* Orion S9 Car Model */ .car-s9 { view-transition-name: orion-s9; } /* Orion E7 Car Model */ .car-e7 { view-transition-name: orion-e7; } Refining Movement with ::view-transition-group
You can target the container managing the named transition (::view-transition-group) to customize timing, curves, and physics:
::view-transition-group(orion-v12), ::view-transition-group(orion-s9), ::view-transition-group(orion-e7) { animation-duration: 0.72s; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); } Key Takeaways
-
@view-transition { navigation: auto; }enables native cross-document transitions across separate same-origin HTML files. - Use
::view-transition-old(root)and::view-transition-new(root)to customize whole-page exit and entry styles. - Assign identical
view-transition-namevalues to elements across pages to seamlessly morph images, headings, and cards. - Keep your architecture clean, fast, and accessible without shipping heavy client-side JavaScript routers.
Take Your CSS to the Next Level 🚀
If you want to build clean, responsive layouts like a senior developer even faster, I packaged these architectural patterns into a practical resource:
👉 Grab the Guide: Build Any Responsive Layout with CSS Grid & Flexbox
It is designed to take you from layout confusion to confidently building any complex layout you see online!
Resources
🎥 Watch the Complete Step-by-Step Tutorial: YouTube Video Walkthrough

