Smooth visual changes when DOM content updates or when navigating between pages. The goal is motion that feels intentional, respects reduced-motion preferences, and does not require rewriting your site as a single-page app.
Same-document transitions
Call document.startViewTransition() around a DOM update. The browser captures old and new snapshots, then cross-fades (or runs custom animations) between them:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.25s;
animation-timing-function: ease;
}
::view-transition-old(root) {
animation-name: fade-out;
}
::view-transition-new(root) {
animation-name: fade-in;
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes fade-in {
from { opacity: 0; }
}
The JavaScript wrapper is small: pass a callback that mutates the DOM. CSS controls how the transition looks. Keep durations short – 200-300ms for UI state changes.
Naming transition groups
Give elements a view-transition-name so they animate independently instead of as one flat screenshot:
.hero-image {
view-transition-name: hero;
}
::view-transition-old(hero),
::view-transition-new(hero) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
::view-transition-group(hero) {
overflow: clip;
}
Only one element per page may use a given name at capture time. Duplicate names cause the transition to fall back or skip that element.
Cross-document (MPA) transitions
Modern browsers can animate between full page loads when both documents opt in:
@view-transition {
navigation: auto;
}
/* shared element on both pages */
.page-title {
view-transition-name: page-title;
}
Matching view-transition-name values on old and new pages create a morph effect – useful for article titles, thumbnails, or persistent headers. Same-origin navigations only. Treat this as progressive enhancement; the page must work with a normal instant navigation.
UX guidelines
- Animate position and opacity – not layout properties that trigger reflow on every frame.
- Do not transition every link click. Reserve motion for gallery swaps, theme toggles, tab panels, and route changes where continuity helps orientation.
- Provide instant fallback when
document.startViewTransitionis missing – run the DOM update anyway.
Reduced motion
Respect prefers-reduced-motion. Skip or shorten animations:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Some users want zero motion. Others want subtle feedback. A 0.01s cross-fade is not a meaningful compromise – turn it off.
When view transitions hurt
Long morphs on large layout shifts feel sluggish. Lists that reorder heavily may look odd morphing every item. Heavy pages with slow paint can stutter during capture. Profile on mid-range hardware before shipping page-wide transitions.
The next tutorial covers animation performance – compositor-friendly properties, will-change discipline, and how to spot jank in DevTools.

