View Transitions API
Create smooth animated transitions between page states or routes using the View Transitions API without JavaScript animation libraries.
Overview
Key Points
- •`document.startViewTransition(callback)` wrap DOM updates for a smooth transition
- •Default transition: cross-fade between old and new state
- •`::view-transition-old(root)` and `::view-transition-new(root)` are the CSS pseudo-elements to animate
- •`view-transition-name: my-name` CSS property on elements enables per-element transitions (e.g., hero images)
- •Named transitions allow specific elements to animate independently (morph from thumbnail to full image)
- •For MPA page transitions: `@view-transition { navigation: auto }` in CSS (Chrome 126+)
- •Reduce motion: always wrap view transition CSS in `@media (prefers-reduced-motion: no-preference)`
Examples
Basic same-document view transition
<div id="content">
<h1>Page 1</h1>
<button onclick="updateContent()">Go to Page 2</button>
</div>
<style>
/* Override the default cross-fade for the root transition */
@media (prefers-reduced-motion: no-preference) {
::view-transition-old(root) {
animation: slide-out 0.3s ease-in forwards;
}
::view-transition-new(root) {
animation: slide-in 0.3s ease-out forwards;
}
@keyframes slide-out {
to { transform: translateX(-100%); opacity: 0; }
}
@keyframes slide-in {
from { transform: translateX(100%); opacity: 0; }
}
}
</style>
<script>
async function updateContent() {
if (!document.startViewTransition) {
// Fallback: no animation, just update
document.getElementById('content').innerHTML = '<h1>Page 2</h1>';
return;
}
const transition = document.startViewTransition(() => {
document.getElementById('content').innerHTML = '<h1>Page 2</h1>';
});
await transition.finished;
console.log('Transition complete');
}
</script>Always provide a no-transition fallback for browsers that do not support startViewTransition.
Hero element transition (card expands to detail view)
<style>
.card { view-transition-name: hero-card; }
.detail { view-transition-name: hero-card; }
@media (prefers-reduced-motion: no-preference) {
/* Named elements animate from their old position to new position */
::view-transition-old(hero-card),
::view-transition-new(hero-card) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
}
</style>
<div class="card" id="card" onclick="expandCard()">
<img src="thumb.jpg" alt="Article thumbnail">
<h3>Article Title</h3>
</div>
<script>
function expandCard() {
document.startViewTransition(() => {
document.getElementById('card').outerHTML = `
<div class="detail">
<img src="full.jpg" alt="Full article image">
<h1>Article Title</h1>
<p>Full article content...</p>
</div>
`;
});
}
</script>Both elements share the same view-transition-name the browser morphs between their shapes and positions.
MPA cross-document view transition
/* In your CSS (e.g., styles.css) applies to all pages */
/* Enable automatic transitions for all navigations */
@view-transition {
navigation: auto;
}
/* Customize: slide instead of fade for page navigations */
@media (prefers-reduced-motion: no-preference) {
@keyframes slide-from-right {
from { transform: translateX(100%); }
}
@keyframes slide-to-left {
to { transform: translateX(-100%); }
}
/* Apply to forward navigation */
::view-transition-new(root) {
animation: slide-from-right 0.3s ease;
}
::view-transition-old(root) {
animation: slide-to-left 0.3s ease;
}
}With @view-transition navigation: auto, ALL page navigations on your site get this transition for free.
Common Mistakes
- Not wrapping view transition CSS in `@media (prefers-reduced-motion: no-preference)` animations must respect the user's motion preference
- Assigning the same view-transition-name to two different visible elements simultaneously names must be unique among currently visible elements
- Not providing a fallback when `document.startViewTransition` is undefined Safari and older browsers don't support it
Note
The transition pseudo-elements live in a separate layer above all page content. This means `z-index` on your page elements does not affect the transition animation layer you can't accidentally put a header on top of the transition.
Exercises
Write HTML to solve each exercise. Use Preview to see it render in a live iframe, then mark as done.
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4