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
Top comments (0)