Home / Snippets / Animation /

Shared element transition

A shared element morphs between list and detail views — hover a card to see the detail panel animate in.

Alice ChenEngineer
Bob RiveraDesigner
Carol ParkProduct

Team member

Hover a card to see the shared element transition animate the avatar from list to detail view.

Experimental
animationexperimental

Quick implementation

/* Shared element: same view-transition-name on both views */
.list-avatar {
  view-transition-name: hero-avatar;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: oklch(0.55 0.18 265);
}

.detail-avatar {
  view-transition-name: hero-avatar;
  width: 8rem;
  height: 8rem;
  border-radius: 50%;
  background: oklch(0.55 0.18 265);
}

/* Custom transition timing */
::view-transition-group(hero-avatar) {
  animation-duration: 0.4s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Enable for cross-document navigations */
@view-transition {
  navigation: auto;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*) {
    animation-duration: 0.01s;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building list-to-detail transitions.

Goal: A shared element (e.g. avatar image) that morphs seamlessly from its position in a list to a larger version in a detail view, using the View Transitions API.

Technical constraints:
- Assign the same view-transition-name to the element in both the list and detail views.
- Each view-transition-name must be unique on the page — only one element can have a given name at a time.
- Customize ::view-transition-group() timing with cubic-bezier(0.4, 0, 0.2, 1).
- Use oklch() for all colors — no hex or rgba().
- Enable cross-document transitions with @view-transition { navigation: auto }.

Framework variant (pick one):
A) MPA — CSS only, the browser handles the snapshot and morph automatically between page loads.
B) React SPA — use document.startViewTransition() in onClick/route handler, toggle DOM between list and detail.

Edge cases to handle:
- Respect prefers-reduced-motion: set animation-duration to near-instant.
- Only one element per page can share a view-transition-name — duplicate names cause the transition to fail silently.
- Fallback for browsers without View Transitions: show/hide with a simple fade.

Return CSS + minimal JS for the SPA variant.

Why this matters in 2026

Shared element transitions — familiar from native mobile apps — are now possible in browsers via the View Transitions API. By assigning the same view-transition-name to an element in two different views, the browser automatically snapshots, positions, and morphs between them. This eliminates the need for FLIP animation libraries and works across page navigations, not just within a single SPA.

The logic

The browser captures the element with a given view-transition-name as a snapshot before the DOM changes. After the update, it finds the same name on the new page, captures its new size and position, and creates a ::view-transition-group(name) pseudo-element that interpolates between the two states. The morphing happens in a compositor layer, so it doesn't trigger layout during the animation. You control the timing via ::view-transition-group(name) CSS rules.

Accessibility & performance

View Transition morphs are compositor-only — no layout or paint during the animation. The API degrades gracefully: browsers without support simply skip the transition. Always wrap custom timing in prefers-reduced-motion. Be cautious with large images — the browser creates bitmap snapshots, so very high-resolution elements may cause memory pressure during the transition.