Skip to main content

vercel-react-view-transitions

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.

跳到安装

来源信息

仓库
VladSez/easy-invoice-pdf
最近来源活动
2026年6月13日 18:16
检测到的 SKILL.md 语言
英语
星标
1,096
分支
103

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
8 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
vercel-react-view-transitions
description
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.
license
MIT
metadata
{"author":"vercel","version":"1.0.0"}
# React View Transitions Animate between UI states using the browser's native `document.startViewTransition`. Declare _what_ with `<ViewTransition>`, trigger _when_ with `startTransition` / `useDeferredValue` / `Suspense`, control _how_ with CSS classes. Unsupported browsers skip animations gracefully. ## When to Animate Every `<ViewTransition>` 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: | Priority | Pattern | What it communicates | | -------- | ---------------------------------- | -------------------------------- | | 1 | **Shared element** (`name`) | "Same thing — going deeper" | | 2 | **Suspense reveal** | "Data loaded" | | 3 | **List identity** (per-item `key`) | "Same items, new arrangement" | | 4 | **State change** (`enter`/`exit`) | "Something appeared/disappeared" | | 5 | **Route change** (layout-level) | "Going to a new place" | This is an implementation order, not a "pick one" list. Implement every pattern that fits the app. Only skip a pattern if the app has no use case for it. ### Choosing Animation Style | Context | Animation | Why | | --------------------------------------- | -------------------------------------------------- | ---------------------------- | | Hierarchical navigation (list → detail) | Type-keyed `nav-forward` / `nav-back` | Communicates spatial depth | | Lateral navigation (tab-to-tab) | Bare `<ViewTransition>` (fade) or `default="none"` | No depth to communicate | | Suspense reveal | `enter`/`exit` string props | Content arriving | | Revalidation / background refresh | `default="none"` | Silent — no animation needed | Reserve directional slides for hierarchical navigation (list → detail) and ordered sequences (prev/next photo, carousel, paginated results). For ordered sequences, the direction communicates position: "next" slides from right, "previous" from left. Lateral/unordered navigation (tab-to-tab) should not use directional slides — it falsely implies spatial depth. --- ## Availability - **Next.js:** Do **not** install `react@canary` — the App Router already bundles React canary internally. `ViewTransition` works out of the box. `npm ls react` may show a stable-looking version; this is expected. - **Without Next.js:** Install `react@canary react-dom@canary` (`ViewTransition` is not in stable 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 `<ViewTransition>` Component ```jsx import { ViewTransition } from "react"; <ViewTransition> <Component /> </ViewTransition>; ``` React auto-assigns a unique `view-transition-name` and calls `document.startViewTransition` behind the scenes. Never call `startViewTransition` yourself. ### Animation Triggers | Trigger | When it fires | | ---------- | ------------------------------------------------------------------------------------------------- | | **enter** | `<ViewTransition>` first inserted during a Transition | | **exit** | `<ViewTransition>` first removed during a Transition | | **update** | DOM mutations inside a `<ViewTransition>`. With nested VTs, mutation applies to the innermost one | | **share** | Named 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 `<ViewTransition>` only activates enter/exit if it appears **before any DOM nodes**: ```jsx // 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> ``` --- ## Styling with View Transition Classes ### Props Values: `"auto"` (browser cross-fade), `"none"` (disabled), `"class-name"` (custom CSS), or `{ [type]: value }` for type-specific animations. ```jsx <ViewTransition default="none" enter="slide-in" exit="slide-out" share="morph" /> ``` 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. Call it multiple times to stack types — different VTs in the tree react to different types: ```jsx startTransition(() => { addTransitionType("nav-forward"); addTransitionType("select-item"); router.push("/detail/1"); }); ``` Pass an object to map types to CSS classes. Works on `enter`, `exit`, **and** `share`: ```jsx <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` and `exit` don't have to be symmetric. For example, fade in but slide out directionally: ```jsx <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` requires a `default` key in the object. For apps with multiple pages, extract the type-keyed VT into a reusable wrapper: ```jsx 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()` and Browser Back Button `router.back()` and the browser's back/forward buttons do **not** trigger view transitions (`popstate` is synchronous, incompatible with `startViewTransition`). Use `router.push()` with an explicit URL instead. ### 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: ```jsx <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> ``` - Only one VT with a given `name` can be mounted at a time — use unique names (`photo-${id}`). Watch for reusable components: if a component with a named VT is rendered in both a modal/popover _and_ a page, both mount simultaneously and break the morph. Either make the name conditional (via a prop) or move the named VT out of the shared component into the specific consumer. - `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 ```jsx { show && ( <ViewTransition enter="fade-in" exit="fade-out"> <Panel /> </ViewTransition> ); } ``` ### List Reorder ```jsx { items.map((item) => ( <ViewTransition key={item.id}> <ItemCard item={item} /> </ViewTransition> )); } ``` Trigger inside `startTransition`. Avoid wrapper `<div>`s between list and VT. ### Composing Shared Elements with List Identity Shared elements and list identity are independent concerns — don't confuse one for the other. When a list item contains a shared element (e.g., an image that morphs into a detail view), use two nested `<ViewTransition>` boundaries: ```jsx { 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> )); } ``` The outer VT handles list reorder/enter animations. The inner VT handles the cross-route shared element morph. Missing either layer means that animation silently doesn't happen. ### Force Re-Enter with `key` ```jsx <ViewTransition key={searchParams.toString()} enter="slide-up" default="none"> <ResultsGrid /> </ViewTransition> ``` **Caution:** If wrapping `<Suspense>`, changing `key` remounts the boundary and refetches. ### Suspense Fallback to Content Simple cross-fade: ```jsx <ViewTransition> <Suspense fallback={<Skeleton />}> <Content /> </Suspense> </ViewTransition> ``` Directional reveal: ```jsx <Suspense fallback={ <ViewTransition exit="slide-down"> <Skeleton /> </ViewTransition> } > <ViewTransition enter="slide-up" default="none"> <Content /> </ViewTransition> </Suspense> ``` For more patterns, see `references/patterns.md`. ---
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看