# View Transitions & Scroll Animations > CSS-native page transitions and scroll-driven animations without JavaScript. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/view-transitions-scroll-animations Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## 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. ## 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. ## Diagram ```mermaid sequenceDiagram participant JS as JS / navigation participant Browser participant Pseudo as ::view-transition tree JS->>Browser: startViewTransition(callback) Browser->>Browser: capture old state Browser->>JS: run callback (DOM update) Browser->>Pseudo: create old + new layers Pseudo->>Browser: animate crossfade (compositor) Browser->>Pseudo: remove pseudo-elements ``` `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-in** ```css /* ─── 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 | Feature | Same-document | Cross-document MPA | | --- | --- | --- | | **Trigger** | `document.startViewTransition(callback)` | `@view-transition { navigation: auto; }` | | **JS required** | Yes — wraps the DOM mutation | No — CSS only | | **Browser support** | Widely supported (Baseline) | Chromium + Safari; Firefox not yet — check caniuse | | **Scope** | SPA route changes, modals, in-page updates | Full page navigations between URLs | ## Watch out: 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. ## 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. ## Related topics - [Advanced CSS Architecture](https://fearchitect.com/topics/advanced-css-architecture.md): Native CSS features that replace methodology and JS for cascade control. - [Render Performance](https://fearchitect.com/topics/render-performance-patterns.md): Skip renders, defer slow work, and virtualize long lists. - [Paint & Composite Optimization](https://fearchitect.com/topics/paint-composite-optimization.md): Animate only transform and opacity to skip paint entirely. - [Atomic / Utility-First CSS](https://fearchitect.com/topics/atomic-utility-first-css.md): Single-purpose classes that compose styles without growing a stylesheet. - [CSS Modules vs CSS-in-JS vs Tailwind](https://fearchitect.com/topics/css-modules-vs-css-in-js-vs-tailwind.md): CSS Modules, CSS-in-JS, and utility-first — by runtime cost and RSC fit. - [Design Systems](https://fearchitect.com/topics/design-system.md): A shared product — tokens, components, docs, and governance — at scale. ## Further reading - [MDN — View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) - [MDN — animation-timeline](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timeline) - [web.dev — Smooth transitions with the View Transitions API](https://developer.chrome.com/docs/web-platform/view-transitions) - [web.dev — Scroll-driven animations](https://developer.chrome.com/docs/css-ui/scroll-driven-animations)