Page Transition Patterns

A page transition is the most expensive animation in a UI: it takes over the entire viewport for around 300–500ms. Done right, it gives the user spatial context — they know where they’re going within the app. Done wrong, it’s just a delay.

Pick a transition type above, then click the nav on the left to change pages

Overview
Typography
Color
Spacing

The purpose of a page transition

A transition isn’t there to look “pretty.” Its purpose is to:

  1. Confirm the action: tap a button → something happens (avoid a frozen feeling)
  2. Spatial context: where the new page sits relative to the old one (drill-in, go back, peer navigation)
  3. Mask loading: hide the time spent fetching data

If a transition doesn’t serve at least one of these three purposes, consider dropping it entirely.

Pattern 1: Fade

The simplest option, and the best fit for navigation with no spatial relationship.

@keyframes page-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.page-enter {
  animation: page-fade-in 0.2s ease-out;
}

Use when: tab navigation, top-level menu items, modal-to-modal. When two pages have no parent–child or before–after relationship.

Avoid: fades that are too long (> 300ms) — users perceive them as lag, not as a transition.

Pattern 2: Slide (direction-aware)

The new page slides in from the direction that matches the action, building a spatial map in the user’s mind.

/* Drill in: new page comes from right */
.page-enter-right {
  animation: slide-from-right 0.3s cubic-bezier(0.2, 0, 0, 1);
}

/* Go back: new page comes from left */
.page-enter-left {
  animation: slide-from-left 0.3s cubic-bezier(0.2, 0, 0, 1);
}

@keyframes slide-from-right {
  from { transform: translateX(24px); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}

@keyframes slide-from-left {
  from { transform: translateX(-24px); opacity: 0; }
  to   { transform: translateX(0);     opacity: 1; }
}

Important note: don’t slide too far. translateX(24px) is enough to create a directional cue — translateX(100vw) is dizzying and far too slow.

Use when: there’s a breadcrumb hierarchy (list → detail → deeper detail), wizard/onboarding steps, or back navigation.

Pattern 3: Shared element transition

An element from the old page “becomes” an element on the new page. This creates the strongest sense of continuity.

// View Transition API (native browser, Chrome 111+)
document.startViewTransition(() => {
  // Update DOM here
  navigateTo(newPage);
});
/* CSS to define the shared element */
.card-image   { view-transition-name: hero-image; }
.detail-image { view-transition-name: hero-image; }

The browser automatically interpolates position and size between two elements that share the same view-transition-name.

Use when: card → detail page, thumbnail → full view, product list → product page.

Fallback: if the browser doesn’t support the View Transition API, fall back to a fade.

if (document.startViewTransition) {
  document.startViewTransition(() => navigateTo(newPage));
} else {
  navigateTo(newPage); // instant, no transition
}

Timing guidelines

Transition typeDurationEasing
Fade150–200msease-out
Slide (small offset)250–350mscubic-bezier(0.2, 0, 0, 1)
Slide (full page)300–400mscubic-bezier(0.2, 0, 0, 1)
Shared element350–500mscubic-bezier(0.2, 0, 0, 1)

Always use ease-out (fast in, slow settle) for the entering transition. The page is “arriving” — fast in feels responsive, slow settle feels controlled.

The exit (the old page leaving) usually uses ease-in, or simply a quick fade-out (100ms).

Common mistakes

Two-directional transitions at once: the old page slides left AT THE SAME TIME the new page slides in from the right → too much motion, and the brain doesn’t know where to focus. Better: fade the old page out quickly, then slide the new page in.

Transitions that are too large on mobile: translateX(100vw) on a small screen → needs > 400ms to cross the screen → feels slow. Scale it down to 20–30% of the width.

Not disabling for prefers-reduced-motion:

@media (prefers-reduced-motion: reduce) {
  .page-enter { animation: none; }
}

Further reading: Cubic Bezier in Depth, Ease vs Spring, Scroll Reveal Patterns.