Skip to content
fearchitect
Styling & Design Systems

View Transitions & Scroll Animations

CSS-native page transitions and scroll-driven animations without JavaScript.

By Abas TurabliReviewed

Summary

The View Transitions API lets the browser crossfade or morph elements between two DOM states using `document.startViewTransition()` (same-document) or the `@view-transition` at-rule (cross-document MPA). Scroll-driven animations bind `animation-timeline` to scroll or element position — no JS, no `IntersectionObserver`. Same-document transitions are widely supported; cross-document transitions and scroll-driven animations are newer — check Baseline/caniuse before relying on them.

Jump to the interview angle

View Transitions & Scroll Animations

View Transitions capture a screenshot of the current DOM state, apply a new state, then animate between the two snapshots using ::view-transition-old and ::view-transition-new pseudo-elements. Same-document transitions use document.startViewTransition(); cross-document (MPA) transitions need only @view-transition { navigation: auto; } in CSS — no JS at all. Scroll-driven animations bind animation-timeline to scroll progress, moving animation playback in sync with the user's scroll, on the compositor thread.

`startViewTransition` captures old state before the callback runs, so the browser always has both snapshots.

Same-document transition + scroll-driven reveal

Assign view-transition-name to the element that should morph. Wrap the DOM update in startViewTransition. For scroll animations, set animation-timeline: view() and use animation-range to control when in the scroll the animation plays.

View transition with scroll-driven fade-incss
/* ─── View transition ─────────────────────────────── */

/* Opt the hero image into a named layer so it morphs. */
.hero-image {
  view-transition-name: hero;
}

/* Override the default crossfade with a slide. */
::view-transition-old(hero) {
  animation: slide-out 0.3s ease-in forwards;
}
::view-transition-new(hero) {
  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; } }

/* Cross-document MPA: one rule, no JS needed. */
@view-transition {
  navigation: auto;
}

/* ─── Scroll-driven animation ──────────────────────── */

.card {
  opacity: 0;
  translate: 0 2rem;
  animation: fade-up linear both;

  /* Bind to how much of .card is in the viewport. */
  animation-timeline: view();

  /* Play when the element is 10%–40% visible. */
  animation-range: entry 10% entry 40%;
}

@keyframes fade-up {
  to { opacity: 1; translate: 0 0; }
}

/* ─── Reduced motion ───────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none;
  }
  .card {
    animation: none;
    opacity: 1;
    translate: 0 0;
  }
}

view-transition-name creates a named capture pair. animation-timeline: view() needs no scroll event listener — the browser drives playback on the compositor. The prefers-reduced-motion block disables both APIs in one place.

Same-document vs cross-document transitions

FeatureSame-documentCross-document MPA
Trigger`document.startViewTransition(callback)``@view-transition { navigation: auto; }`
JS requiredYes — wraps the DOM mutationNo — CSS only
Browser supportWidely supported (Baseline)Chromium + Safari; Firefox not yet — check caniuse
ScopeSPA route changes, modals, in-page updatesFull page navigations between URLs

Reduced motion and unique names

Every view-transition-name on the page must be unique at the moment the transition fires; duplicates silently skip the morph. Always wrap transition overrides in @media (prefers-reduced-motion: reduce) and set animation: none on both ::view-transition-old(*) and ::view-transition-new(*) — the browser does not suppress them automatically.

Interview angle

Interviewers ask about MPA cross-document support, the ::view-transition-* pseudo-element tree, and why scroll-driven animations beat IntersectionObserver for paint-free effects. Soundbite: "startViewTransition snapshots the old and new state; the browser runs the crossfade on the compositor — zero JS animation loop needed."

Key terms

view-transition-name
CSS property that opts an element into a named transition layer, pairing old and new states for morphing.
::view-transition-old / ::view-transition-new
Pseudo-elements holding the captured screenshot of the outgoing and incoming state during a transition.
animation-timeline: scroll()
Links an animation's progress to a scroll container's scroll position; no JS required.
animation-timeline: view()
Links animation progress to how much of an element is visible inside a scroll container.
@view-transition
CSS at-rule that enables cross-document (MPA) view transitions without any JavaScript.

Further reading

Search fearchitect

Jump to a topic, mode, or action.