vercel-react-view-transitions

Par vercel-labs · agent-skills

Guide pour implémenter des animations fluides et natives avec l'API View Transition de React (composant `<ViewTransition>`, `addTransitionType`, et pseudo-éléments CSS de view transition). Utilisez cette skill chaque fois que l'utilisateur souhaite ajouter des transitions de page, animer des changements de route, créer des animations d'éléments partagés, animer l'entrée/sortie de composants, animer le réordonnancement de listes, implémenter des animations de navigation directionnelles (avant/arrière), ou intégrer des view transitions dans Next.js. À utiliser également lorsque l'utilisateur mentionne les view transitions, `startViewTransition`, `ViewTransition`, les types de transition, ou demande comment animer des changements d'état d'interface dans React sans bibliothèques d'animation tierces.

npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-view-transitions

Transitions de vue React

Animez les changements d'état de l'interface en utilisant le natif document.startViewTransition du navigateur. Déclarez le quoi avec <ViewTransition>, déclenchez le quand avec startTransition / useDeferredValue / Suspense, contrôlez le comment avec les classes CSS. Les navigateurs non supportés ignorent gracieusement les animations.

Quand animer

Chaque <ViewTransition> doit communiquer une relation spatiale ou une continuité. Si vous ne pouvez pas articuler ce qu'elle communique, ne l'ajoutez pas.

Implémentez tous les motifs applicables de cette liste, dans cet ordre :

Priorité Motif Ce qu'il communique
1 Élément partagé (name) « La même chose — en approfondissant »
2 Suspense reveal « Les données ont été chargées »
3 Identité de liste (key par élément) « Les mêmes éléments, nouvel arrangement »
4 Changement d'état (enter/exit) « Quelque chose a disparu/réapparu »
5 Changement de route (au niveau du layout) « Aller à un nouvel endroit »

C'est un ordre d'implémentation, pas une liste « choisir un ». Implémentez chaque motif qui s'applique à l'app. Ne sautez un motif que s'il n'y a pas de cas d'usage pour lui.

Choisir le style d'animation

Contexte Animation Pourquoi
Navigation hiérarchique (liste → détail) nav-forward / nav-back typé par clé Communique la profondeur spatiale
Navigation latérale (onglet à onglet) <ViewTransition> nu (fade) ou default="none" Pas de profondeur à communiquer
Suspense reveal Props string enter/exit Contenu arrivant
Revalidation / rafraîchissement en arrière-plan default="none" Silencieux — aucune animation nécessaire

Réservez les slides directionnels pour la navigation hiérarchique (liste → détail) et les séquences ordonnées (photo précédente/suivante, carousel, résultats paginés). Pour les séquences ordonnées, la direction communique la position : « suivant » glisse de droite, « précédent » de gauche. La navigation latérale/non-ordonnée (onglet à onglet) ne doit pas utiliser de slides directionnels — cela implique faussement une profondeur spatiale.


Disponibilité

  • Next.js : Ne pas installer react@canary — l'App Router regroupe déjà React canary en interne. ViewTransition fonctionne prêt à l'emploi. npm ls react peut afficher une version ressemblant à stable ; c'est attendu.
  • Sans Next.js : Installez react@canary react-dom@canary (ViewTransition n'est pas dans React stable).
  • Support navigateur : Chromium 111+, Firefox 144+, Safari 18.2+. Dégradation gracieuse sur les navigateurs non supportés.

Flux de travail d'implémentation

Quand vous ajoutez des transitions de vue à une app existante, suivez references/implementation.md étape par étape. Commencez par l'audit — ne le sautez pas. Copiez les recettes CSS de references/css-recipes.md dans la feuille de style globale — n'écrivez pas votre propre CSS d'animation.


Concepts fondamentaux

Le composant <ViewTransition>

import { ViewTransition } from 'react';

<ViewTransition>
  <Component />
</ViewTransition>

React attribue automatiquement un view-transition-name unique et appelle document.startViewTransition en arrière-plan. N'appelez jamais startViewTransition vous-même.

Déclencheurs d'animation

Déclencheur Quand il se déclenche
enter <ViewTransition> inséré pour la première fois pendant une Transition
exit <ViewTransition> supprimé pour la première fois pendant une Transition
update Mutations DOM à l'intérieur d'une <ViewTransition>. Avec VT imbriquées, la mutation s'applique à la plus interne
share VT nommée se démonte et une autre avec le même name se monte dans la même Transition

Seuls startTransition, useDeferredValue ou Suspense activent les VT. Un setState régulier n'anime pas.

Règle de placement critique

<ViewTransition> active enter/exit seulement si elle apparaît avant tous les nœuds DOM :

// Fonctionne
<ViewTransition enter="auto" exit="auto">
  <div>Content</div>
</ViewTransition>

// Cassé — div enveloppe la VT, supprimant enter/exit
<div>
  <ViewTransition enter="auto" exit="auto">
    <div>Content</div>
  </ViewTransition>
</div>

Styliser avec les classes de transition de vue

Props

Valeurs : "auto" (cross-fade du navigateur), "none" (désactivé), "class-name" (CSS personnalisé), ou { [type]: value } pour les animations type-spécifiques.

<ViewTransition default="none" enter="slide-in" exit="slide-out" share="morph" />

Si default est "none", tous les déclencheurs sont désactivés sauf s'ils sont explicitement listés.

Pseudo-éléments CSS

  • ::view-transition-old(.class) — snapshot sortant
  • ::view-transition-new(.class) — snapshot entrant
  • ::view-transition-group(.class) — conteneur
  • ::view-transition-image-pair(.class) — paire old + new

Voir references/css-recipes.md pour les recettes d'animation prêtes à l'emploi.


Types de transition

Étiquetez les transitions avec addTransitionType pour que les VT puissent choisir différentes animations en fonction du contexte. Appelez-la plusieurs fois pour empiler les types — différentes VT dans l'arbre réagissent à différents types :

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

Passez un objet pour mapper les types aux classes CSS. Fonctionne sur enter, exit, et share :

<ViewTransition
  enter={{ 'nav-forward': 'slide-from-right', 'nav-back': 'slide-from-left', default: 'none' }}
  exit={{ 'nav-forward': 'slide-to-left', 'nav-back': 'slide-to-right', default: 'none' }}
  share={{ 'nav-forward': 'morph-forward', 'nav-back': 'morph-back', default: 'morph' }}
  default="none"
>
  <Page />
</ViewTransition>

enter et exit ne doivent pas être symétriques. Par exemple, fade in mais slide out directionnellement :

<ViewTransition
  enter={{ 'nav-forward': 'fade-in', 'nav-back': 'fade-in', default: 'none' }}
  exit={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
  default="none"
>

TypeScript : ViewTransitionClassPerType nécessite une clé default dans l'objet.

Pour les apps avec plusieurs pages, extrayez la VT typée par clé dans un wrapper réutilisable :

export function DirectionalTransition({ children }: { children: React.ReactNode }) {
  return (
    <ViewTransition
      enter={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
      exit={{ 'nav-forward': 'nav-forward', 'nav-back': 'nav-back', default: 'none' }}
      default="none"
    >
      {children}
    </ViewTransition>
  );
}

router.back() et bouton de retour du navigateur

router.back() et les boutons de retour/avant du navigateur ne pas déclencher les transitions de vue (popstate est synchrone, incompatible avec startViewTransition). Utilisez router.push() avec une URL explicite à la place.

Types et Suspense

Les types sont disponibles pendant la navigation mais pas lors des révélations Suspense ultérieures (transitions séparées, pas de type). Utilisez les cartes de type pour enter/exit au niveau page ; utilisez des props string simples pour les révélations Suspense.


Transitions d'élément partagé

Le même name sur deux VT — l'une se démontant, l'autre se montant — crée une morphe d'élément partagé :

<ViewTransition name="hero-image">
  <img src="/thumb.jpg" onClick={() => startTransition(() => onSelect())} />
</ViewTransition>

// Sur l'autre vue — même name
<ViewTransition name="hero-image">
  <img src="/full.jpg" />
</ViewTransition>
  • Une seule VT avec un name donné peut être montée à la fois — utilisez des noms uniques (photo-${id}). Attention aux composants réutilisables : si un composant avec une VT nommée est rendu à la fois dans une modal/popover et une page, les deux se montent simultanément et cassent la morphe. Soit rendez le name conditionnel (via une prop), soit déplacez la VT nommée hors du composant partagé vers le consommateur spécifique.
  • share prend précédence sur enter/exit. Réfléchissez à chaque chemin de navigation : quand aucune paire correspondante ne se forme (par ex., la page cible n'a pas le même name), enter/exit se déclenche à la place. Considérez si l'élément a besoin d'une animation de secours pour ces chemins.
  • N'utilisez jamais une exit fade-out sur les pages avec morphes partagées — utilisez un slide directionnel à la place.

Motifs courants

Enter/Exit

{show && (
  <ViewTransition enter="fade-in" exit="fade-out"><Panel /></ViewTransition>
)}

Réordonner une liste

{items.map(item => (
  <ViewTransition key={item.id}><ItemCard item={item} /></ViewTransition>
))}

Déclenchez à l'intérieur de startTransition. Évitez les <div> wrapper entre la liste et la VT.

Composer les éléments partagés avec l'identité de liste

Les éléments partagés et l'identité de liste sont des préoccupations indépendantes — ne confondez pas l'une avec l'autre. Quand un élément de liste contient un élément partagé (par ex., une image qui morphe en vue détail), utilisez deux limites <ViewTransition> imbriquées :

{items.map(item => (
  <ViewTransition key={item.id}>                                      {/* identité de liste */}
    <Link href={`/items/${item.id}`}>
      <ViewTransition name={`item-image-${item.id}`} share="morph">   {/* élément partagé */}
        <Image src={item.image} />
      </ViewTransition>
      <p>{item.name}</p>
    </Link>
  </ViewTransition>
))}

La VT extérieure gère les réordonnancements de liste/animations enter. La VT intérieure gère la morphe d'élément partagé inter-routes. Manquer l'une ou l'autre couche signifie que l'animation ne se produira pas silencieusement.

Forcer la re-entrée avec key

<ViewTransition key={searchParams.toString()} enter="slide-up" default="none">
  <ResultsGrid />
</ViewTransition>

Attention : Si vous enveloppez <Suspense>, changer key remonte la limite et refait la requête.

Suspense Fallback to Content

Cross-fade simple :

<ViewTransition>
  <Suspense fallback={<Skeleton />}><Content /></Suspense>
</ViewTransition>

Révélation directionnelle :

<Suspense fallback={<ViewTransition exit="slide-down"><Skeleton /></ViewTransition>}>
  <ViewTransition enter="slide-up" default="none"><Content /></ViewTransition>
</Suspense>

Pour plus de motifs, voir references/patterns.md.


Comment plusieurs VT interagissent

Chaque VT correspondant au déclencheur se déclenche simultanément dans un seul document.startViewTransition. Les VT dans différentes transitions (navigation vs résolution Suspense ultérieure) ne se font pas concurrence.

Utiliser default="none" généreusement

Sans lui, chaque VT se déclenche en cross-fade navigateur sur chaque transition — résolutions Suspense, mises à jour useDeferredValue, revalidations en arrière-plan. Utilisez toujours default="none" et activez explicitement seulement les déclencheurs désirés.

Deux motifs coexistent

Motif A — Slides directionnels : VT typée par clé sur chaque page, se déclenche pendant la navigation. Motif B — Suspense reveals : Props string simples, se déclenche quand les données se chargent (pas de type).

Ils coexistent parce qu'ils se déclenchent à différents moments. default="none" sur les deux empêche l'interférence croisée. Associez toujours enter avec exit. Placez les VT directionnelles dans les composants page, pas les layouts.

Limitation des VT imbriquées

Quand une VT parent se démonte, les VT imbriquées à l'intérieur ne se déclenchent pas leurs propres enter/exit — seule la VT la plus externe s'anime. Les animations staggerées par élément pendant la navigation page ne sont pas possibles aujourd'hui. Voir react#36135 pour un opt-in expérimental.


Intégration Next.js

Pour le setup Next.js (drapeau experimental.viewTransition, prop transitionTypes sur next/link, motifs App Router, Server Components), voir references/nextjs.md.


Accessibilité

Ajoutez toujours le CSS reduced motion de references/css-recipes.md à votre feuille de style globale.


Fichiers de référence

  • references/implementation.md — Flux de travail d'implémentation étape par étape.
  • references/patterns.md — Motifs, timing d'animation, events API, dépannage.
  • references/css-recipes.md — Recettes d'animation CSS prêtes à l'emploi.
  • references/nextjs.md — Motifs App Router Next.js et détails des Server Components.

Document compilé complet

Pour le guide complet avec tous les fichiers de référence développés : AGENTS.md

Skills similaires