All skills
Skillintermediate

React View Transitions

Animate between UI states using the browser's native `document.startViewTransition`. Declare *what* with ``, trigger *when* with `startTransition` / `useDeferredValue` / `Suspense`, control *how* with CSS classes. Unsupported browsers skip animations gracefully.

Claude Code Knowledge Pack7/10/2026

Overview

React View Transitions

Animate between UI states using the browser's native document.startViewTransition. Declare what with ``, trigger when with startTransition / useDeferredValue / Suspense, control how with CSS classes. Unsupported browsers skip animations gracefully.

When to Animate

Every `` should communicate a spatial relationship or continuity. If you can't articulate what it communicates, don't add it.

Implement all applicable patterns from this list, in this order:

PriorityPatternWhat it communicates
1Shared element (name)"Same thing — going deeper"
2Suspense reveal"Data loaded"
3List identity (per-item key)"Same items, new arrangement"
4State change (enter/exit)"Something appeared/disappeared"
5Route change (layout-level)"Going to a new place"

This is an implementation order, not a "pick one" list. Most apps need #1–#3 at minimum. Only skip a pattern if the app has no use case for it. Only one tree level should animate at a time — adding a layout-level transition on top of per-page animations produces competing double-animation.

Choosing Animation Style

ContextAnimationWhy
Hierarchical navigation (list → detail)Type-keyed nav-forward / nav-backCommunicates spatial depth
Lateral navigation (tab-to-tab)Bare `` (fade) or default="none"No depth to communicate
Suspense revealenter/exit string propsContent arriving
Revalidation / background refreshdefault="none"Silent — no animation needed

Reserve directional slides for hierarchical navigation only. Directional slides on sibling links falsely imply spatial depth.


Availability

  • Requires react@canary or react@experimentalnot in stable React (including 19.x). Verify with npm ls react.
  • Browser support: Chromium 111+, Firefox 144+, Safari 18.2+. Graceful degradation on unsupported browsers.

Implementation Workflow

When adding view transitions to an existing app, follow references/implementation.md step by step. Start with the audit — do not skip it. Copy the CSS recipes from references/css-recipes.md into the global stylesheet — do not write your own animation CSS.


Core Concepts

The `` Component


  

React auto-assigns a unique view-transition-name and calls document.startViewTransition behind the scenes. Never call startViewTransition yourself.

Animation Triggers

TriggerWhen it fires
enter`` first inserted during a Transition
exit`` first removed during a Transition
updateDOM mutations inside a ``. With nested VTs, mutation applies to the innermost one
shareNamed VT unmounts and another with same name mounts in the same Transition

Only startTransition, useDeferredValue, or Suspense activate VTs. Regular setState does not animate.

Critical Placement Rule

`` only activates enter/exit if it appears before any DOM nodes:

// Works

  <div>Content</div>

// Broken — div wraps the VT, suppressing enter/exit
<div>
  
    <div>Content</div>
  
</div>

Styling with View Transition Classes

Props

Values: "auto" (browser cross-fade), "none" (disabled), "class-name" (custom CSS), or { [type]: value } for type-specific animations.

If default is "none", all triggers are off unless explicitly listed.

CSS Pseudo-Elements

  • ::view-transition-old(.class) — outgoing snapshot
  • ::view-transition-new(.class) — incoming snapshot
  • ::view-transition-group(.class) — container
  • ::view-transition-image-pair(.class) — old + new pair

See references/css-recipes.md for ready-to-use animation recipes.


Transition Types

Tag transitions with addTransitionType so VTs can pick different animations based on context:

startTransition(() => {
  addTransitionType('nav-forward');
  router.push('/detail/1');
});

Pass an object to map types to CSS classes:


  

TypeScript: ViewTransitionClassPerType requires a default key in the object.

Types and Suspense

Types are available during navigation but not during subsequent Suspense reveals (separate transitions, no type). Use type maps for page-level enter/exit; use simple string props for Suspense reveals.


Shared Element Transitions

Same name on two VTs — one unmounting, one mounting — creates a shared element morph:


  <img src="/thumb.jpg" onClick={() => startTransition(() => onSelect())} />

// On the other view — same name

  <img src="/full.jpg" />

  • Only one VT with a given name can be mounted at a time — use unique names (photo-${id}).
  • share takes precedence over enter/exit. Think through each navigation path: when no matching pair forms (e.g., the target page doesn't have the same name), enter/exit fires instead. Consider whether the element needs a fallback animation for those paths.
  • Never use a fade-out exit on pages with shared morphs — use a directional slide instead.

Common Patterns

Enter/Exit

{show && (
  
)}

List Reorder

{items.map(item => (
  
))}

Trigger inside startTransition. Avoid wrapper <div>s between list and VT.

Force Re-Enter with key


  

Caution: If wrapping ``, changing key remounts the boundary and refetches.

Suspense Fallback to Content

Simple cross-fade:


  }>

Directional reveal:

}>
  

For more patterns, see references/patterns.md.


How Multiple VTs Interact

Every VT matching the trigger fires simultaneously in a single document.startViewTransition. VTs in different transitions (navigation vs later Suspense resolve) don't compete.

Use default="none" Liberally

Without it, every VT fires the browser cross-fade on every transition — Suspense resolves, useDeferredValue updates, background revalidations. Always use default="none" and explicitly enable only desired triggers.

Two Patterns Coexist

Pattern A — Directional slides: Type-keyed VT on each page, fires during navigation. Pattern B — Suspense reveals: Simple string props, fires when data loads (no type).

They coexist because they fire at different moments. default="none" on both prevents cross-interference. Always pair enter with exit. Place directional VTs in page components, not layouts.


Next.js Integration

works out of the box for `startTransition`/`Suspense` updates. To also animate navigations:

// next.config.js
experimental: { viewTransition: true }

This wraps every `` navigation in document.startViewTransition. Use default="none" to prevent competing animations.

next/link supports a native transitionTypes prop:

View

For App Router patterns and Server Component details, see references/nextjs.md.


Accessibility

Always add the reduced motion CSS from references/css-recipes.md to your global stylesheet.


Reference Files

  • references/implementation.md — Step-by-step implementation workflow.
  • references/patterns.md — Patterns, animation timing, events API, troubleshooting.
  • references/css-recipes.md — Ready-to-use CSS animation recipes.
  • references/nextjs.md — Next.js App Router patterns and Server Component details.