Loading...
Loading...
Guide for implementing smooth, native-feeling animations using React's View Transition API (`<ViewTransition>` component, `addTransitionType`, and CSS view transition pseudo-elements). Use this skill whenever the user wants to add page transitions, animate route changes, create shared element animations, animate enter/exit of components, animate list reorder, implement directional (forward/back) navigation animations, or integrate view transitions in Next.js. Also use when the user mentions view transitions, `startViewTransition`, `ViewTransition`, transition types, or asks about animating between UI states in React without third-party animation libraries.
npx skill4agent add vercel-labs/claude-skills vercel-react-view-transitionsdocument.startViewTransition<ViewTransition>startTransitionuseDeferredValueSuspense<ViewTransition>| Priority | Pattern | What it communicates |
|---|---|---|
| 1 | Shared element ( | "Same thing — going deeper" |
| 2 | Suspense reveal | "Data loaded" |
| 3 | List identity (per-item | "Same items, new arrangement" |
| 4 | State change ( | "Something appeared/disappeared" |
| 5 | Route change (layout-level) | "Going to a new place" |
| Context | Animation | Why |
|---|---|---|
| Hierarchical navigation (list → detail) | Type-keyed | Communicates spatial depth |
| Lateral navigation (tab-to-tab) | Bare | No depth to communicate |
| Suspense reveal | | Content arriving |
| Revalidation / background refresh | | Silent — no animation needed |
react@canaryViewTransitionnpm ls reactreact@canary react-dom@canaryViewTransitionreferences/implementation.mdreferences/css-recipes.md<ViewTransition>import { ViewTransition } from 'react';
<ViewTransition>
<Component />
</ViewTransition>view-transition-namedocument.startViewTransitionstartViewTransition| Trigger | When it fires |
|---|---|
| enter | |
| exit | |
| update | DOM mutations inside a |
| share | Named VT unmounts and another with same |
startTransitionuseDeferredValueSuspensesetState<ViewTransition>// Works
<ViewTransition enter="auto" exit="auto">
<div>Content</div>
</ViewTransition>
// Broken — div wraps the VT, suppressing enter/exit
<div>
<ViewTransition enter="auto" exit="auto">
<div>Content</div>
</ViewTransition>
</div>"auto""none""class-name"{ [type]: value }<ViewTransition default="none" enter="slide-in" exit="slide-out" share="morph" />default"none"::view-transition-old(.class)::view-transition-new(.class)::view-transition-group(.class)::view-transition-image-pair(.class)references/css-recipes.mdaddTransitionTypestartTransition(() => {
addTransitionType('nav-forward');
addTransitionType('select-item');
router.push('/detail/1');
});enterexitshare<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>enterexit<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"
>ViewTransitionClassPerTypedefaultexport 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()router.back()popstatestartViewTransitionrouter.push()name<ViewTransition name="hero-image">
<img src="/thumb.jpg" onClick={() => startTransition(() => onSelect())} />
</ViewTransition>
// On the other view — same name
<ViewTransition name="hero-image">
<img src="/full.jpg" />
</ViewTransition>namephoto-${id}shareenterexitenterexit{show && (
<ViewTransition enter="fade-in" exit="fade-out"><Panel /></ViewTransition>
)}{items.map(item => (
<ViewTransition key={item.id}><ItemCard item={item} /></ViewTransition>
))}startTransition<div><ViewTransition>{items.map(item => (
<ViewTransition key={item.id}> {/* list identity */}
<Link href={`/items/${item.id}`}>
<ViewTransition name={`item-image-${item.id}`} share="morph"> {/* shared element */}
<Image src={item.image} />
</ViewTransition>
<p>{item.name}</p>
</Link>
</ViewTransition>
))}key<ViewTransition key={searchParams.toString()} enter="slide-up" default="none">
<ResultsGrid />
</ViewTransition><Suspense>key<ViewTransition>
<Suspense fallback={<Skeleton />}><Content /></Suspense>
</ViewTransition><Suspense fallback={<ViewTransition exit="slide-down"><Skeleton /></ViewTransition>}>
<ViewTransition enter="slide-up" default="none"><Content /></ViewTransition>
</Suspense>references/patterns.mddocument.startViewTransitiondefault="none"useDeferredValuedefault="none"default="none"enterexitexperimental.viewTransitiontransitionTypesnext/linkreferences/nextjs.mdreferences/css-recipes.mdreferences/implementation.mdreferences/patterns.mdreferences/css-recipes.mdreferences/nextjs.mdAGENTS.md