Codeskill

Learn to code, step by step

View transitions and page transition UX

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.startViewTransition is 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.

PreviousAdvanced container query patterns