🚀AdvancedAdvanced Animations

CSS View Transitions

Create smooth animated transitions between page states and DOM changes using the View Transitions API with CSS control.

Overview

The View Transitions API lets you animate DOM changes and page navigations with a cross-fade by default, or fully custom animations you define in CSS. **Same-document transitions (SPA):** ```js document.startViewTransition(() => { // Any DOM update here updateDOM(); }); ``` The browser captures a screenshot before and after, then crossfades between them. You control the animation with CSS pseudo-elements: ```css /* The whole-page crossfade */ ::view-transition-old(root) { animation: fade-out 0.3s ease; } ::view-transition-new(root) { animation: fade-in 0.3s ease; } ``` **Named transitions for specific elements:** ```css .card { view-transition-name: hero-card; /* unique per page */ } /* Target by name */ ::view-transition-old(hero-card) { ... } ::view-transition-new(hero-card) { ... } ``` **Multi-page (MPA) transitions** use the `@view-transition` rule: ```css @view-transition { navigation: auto; } ```

Key Points

  • •document.startViewTransition(callback) captures before/after states and animates between them
  • •The default transition is a crossfade of the entire page
  • •view-transition-name on an element creates a named transition group for that element
  • •view-transition-name values must be unique on the page at the time of the transition
  • •::view-transition-old(name) targets the outgoing snapshot; ::view-transition-new(name) targets the incoming
  • •@view-transition { navigation: auto } enables transitions on same-origin navigations in MPA mode
  • •Transitions respect prefers-reduced-motion automatically if you use the @media reduced-motion check
  • •Supported in Chrome 111+ and Safari 18+ for same-document; MPA support is newer

Examples

Basic page crossfade with custom timing

/* Default crossfade is instant but you can customize it */
::view-transition-old(root) {
  animation: 300ms ease both fade-out;
}

::view-transition-new(root) {
  animation: 300ms ease both fade-in;
}

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

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

/* Respect user motion preference */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

Slide transition for a single-page app

/* Elements get their own transition groups */
.page-content {
  view-transition-name: main-content;
}

/* Slide old content left, new content from right */
::view-transition-old(main-content) {
  animation: 350ms ease both slide-out-left;
}

::view-transition-new(main-content) {
  animation: 350ms ease both slide-in-right;
}

@keyframes slide-out-left {
  to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in-right {
  from { transform: translateX(100%); opacity: 0; }
}

The animation runs automatically; the JavaScript just calls document.startViewTransition(() => { navigate() }).

Hero element transition (shared element animation)

/* The card in the list */
.card-thumb {
  view-transition-name: none; /* set dynamically */
}

/* When a specific card is clicked, assign its name */
/* In JS: card.style.viewTransitionName = "selected-card" */

/* The detail view hero */
.detail-hero {
  view-transition-name: selected-card;
}

/* The browser morphs from card thumbnail to detail hero */
::view-transition-old(selected-card) {
  animation: 400ms ease both scale-down;
}

::view-transition-new(selected-card) {
  animation: 400ms ease both scale-up;
}

@keyframes scale-down {
  to { transform: scale(0.5); opacity: 0; }
}

@keyframes scale-up {
  from { transform: scale(0.5); opacity: 0; }
}

When the same view-transition-name appears in both old and new DOM states, the browser automatically creates a smooth morph between their positions and sizes.

Multi-page transitions with @view-transition

/* Add this to your global CSS file */
@view-transition {
  navigation: auto;
}

/* Customize the transition */
::view-transition-old(root) {
  animation: 200ms ease both page-fade-out;
}

::view-transition-new(root) {
  animation: 200ms ease 100ms both page-fade-in;
}

@keyframes page-fade-out {
  to { opacity: 0; transform: translateY(-8px); }
}

@keyframes page-fade-in {
  from { opacity: 0; transform: translateY(8px); }
}

With @view-transition { navigation: auto }, same-origin page navigations automatically become animated transitions without any JavaScript.

Common Mistakes

  • Assigning the same view-transition-name to multiple visible elements (must be unique per snapshot)
  • Not handling prefers-reduced-motion, making transitions inaccessible to users with vestibular disorders
  • Expecting startViewTransition to work on cross-origin navigations (only same-origin is supported)
  • Using very long or complex animations that block the user from interacting with the new state

Note

For hero transitions (like a thumbnail expanding to a full detail view), assign the view-transition-name in JavaScript just before calling startViewTransition, then remove it after. This avoids name conflicts when multiple items are present.

Exercises

Write CSS to style the HTML scaffold. The preview updates live — when it looks right, mark as done.

Loading editor…

Completed: 0 / 2 exercises

Knowledge Check

Question 1 of 3

What does document.startViewTransition(callback) do?