vercel-react-view-transitions について
React アプリに View Transitions を導入するためのスキルです。画面の関係性に合うアニメーションの選び方から、実装手順、ブラウザー対応、アクセシビリティまで案内します。
主な機能
- 01共有要素のトランジション
- 02Suspense 表示アニメーション
- 03リストと状態のトランジション
- 04CSS によるトランジション設定
- 05ブラウザー対応ガイド
- 06Next.js 統合ガイド
活用シーン
- 01リストから詳細ページへの遷移をアニメーション化
- 02Suspense のデータ解決時に内容を表示
- 03並べ替えや変更のあるリストをアニメーション化
- 04既存の React アプリにトランジションを追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vercel-labs/ agent-skills --skill vercel-react-view-transitionsインストール後、Claude Code で/vercel-react-view-transitionsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
AGENTS.mdmetadata.jsonREADME.mdreferences/css-recipes.mdimplementation.mdnextjs.mdpatterns.mdtroubleshooting.md詳細
React View Transitions
ブラウザー標準の document.startViewTransition を使って、UI の状態間をアニメーションで切り替えます。<ViewTransition> で何をするかを宣言し、startTransition/useDeferredValue/Suspense でいつ開始するかを指定し、CSS クラスでどのように動かすかを制御します。非対応のブラウザーでは、アニメーションをスキップして問題なく動作します。
アニメーションを付けるタイミング
各 <ViewTransition> は、空間的な関係または連続性を伝える必要があります。何を伝えるのか説明できない場合は、追加しないでください。
次のリストにある該当するパターンを、すべてこの順序で実装してください。
| 優先度 | パターン | 伝える内容 |
|---|---|---|
| 1 | 共有要素(name) | 「同じものをさらに深く見る」 |
| 2 | Suspense による表示 | 「データが読み込まれた」 |
| 3 | リストの同一性(項目ごとの key) | 「同じ項目が新しい順序に並んだ」 |
| 4 | 状態の変化(enter/exit) | 「何かが現れた/消えた」 |
| 5 | ルートの変更(ページレベル) | 「新しい場所へ移動する」 |
これは実装の順序であり、「どれか一つを選ぶ」リストではありません。アプリに適用できるパターンは、すべて実装してください。アプリにユースケースがない場合に限り、そのパターンを省略してください。
アニメーションスタイルの選び方
| コンテキスト | アニメーション | 理由 |
|---|---|---|
| 階層的なナビゲーション(リスト → 詳細) | 型ごとの nav-forward/nav-back | 空間的な奥行きを伝えます |
| 横方向のナビゲーション(タブ間) | 裸の <ViewTransition>(フェード)または default="none" | 伝えるべき奥行きがありません |
| Suspense による表示 | enter/exit 文字列プロパティ | コンテンツの表示を表します |
| 再検証/バックグラウンド更新 | default="none" | 目立たない処理なので、アニメーションは不要です |
方向を示すスライドは、階層的なナビゲーション(リスト → 詳細)と順序のあるシーケンス(前/次の写真、カルーセル、ページ分割された結果)に限って使用してください。順序のあるシーケンスでは、方向が位置を伝えます。「次」は右から、「前」は左からスライドします。横方向または順序のないナビゲーション(タブ間)では、方向を示すスライドを使わないでください。空間的な奥行きがあるかのように誤って伝えてしまいます。
利用可能状況
- Next.js:
react@canaryはインストールしないでください。App Router には React canary がすでに内部でバンドルされています。ViewTransitionはそのまま動作します。npm ls reactで安定版のようなバージョンが表示されることがありますが、これは想定どおりです。 - Next.js を使用しない場合: 安定版の
reactとreact-dom19.3 以降を使用してください(ViewTransitionとaddTransitionTypeは React 19.3 で安定版になりました)。それ以前の安定版 React には、これらの API は含まれていません。 - ブラウザーの対応状況:Chromium 125 以降(React には
startViewTransitionの v2 オブジェクト形式が必要です)、Firefox 144 以降、Safari 18.2 以降。非対応のブラウザーでも問題なく動作します。
実装ワークフロー
既存のアプリにビュー遷移を追加する場合は、references/implementation.md に従って、手順を一つずつ進めてください。 まず監査から始めてください。省略してはいけません。該当する CSS には references/css-recipes.md を使い、アプリに合わせて調整してください。
基本概念
<ViewTransition> コンポーネント
import { ViewTransition } from 'react';
<ViewTransition>
<Component />
</ViewTransition>
React は一意の view-transition-name を自動で割り当て、内部で document.startViewTransition を呼び出します。自分で startViewTransition を呼び出さないでください。
アニメーションのトリガー
| トリガー | 発生するタイミング |
|---|---|
| enter | Transition 中に <ViewTransition> が初めて挿入されたとき |
| exit | Transition 中に <ViewTransition> が初めて削除されたとき |
| update | <ViewTransition> 内部で DOM が変更されたとき、または直近の兄弟要素によって境界自体のサイズ/位置が変化したとき。VT がネストされている場合、変更は最も内側の VT に適用されます |
| share | 名前付き VT がアンマウントされ、同じ Transition 内で同じ name を持つ別の VT がマウントされたとき |
VT を有効にするのは、startTransition、useDeferredValue、または Suspense のみです。通常の setState ではアニメーションしません。
重要な配置ルール
<ViewTransition> は、DOM ノードより前に置かれている場合にのみ、enter/exit を有効にします。
// 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>
View Transition クラスを使ったスタイリング
Props
値: "auto"(ブラウザのクロスフェード)、"none"(無効)、"class-name"(カスタム CSS)、または型ごとのアニメーションを指定する { [type]: value }。
<ViewTransition default="none" enter="slide-in" exit="slide-out" share="morph" />
default が "none" の場合、明示的に列挙されない限りすべてのトリガーはオフになります。
CSS 擬似要素
::view-transition-old(.class)— 遷移元のスナップショット::view-transition-new(.class)— 遷移先のスナップショット::view-transition-group(.class)— コンテナー::view-transition-image-pair(.class)— 遷移元と遷移先のペア
すぐに使えるアニメーションのレシピは references/css-recipes.md を参照してください。
遷移タイプ
addTransitionType で遷移にタグを付けると、VT がコンテキストに応じて異なるアニメーションを選択できるようになります。複数回呼び出すとタイプを重ねられ、ツリー内の異なる VT が異なるタイプに反応します:
startTransition(() => {
addTransitionType('nav-forward');
addTransitionType('select-item');
router.push('/detail/1');
});
オブジェクトを渡すと、タイプを CSS クラスにマッピングできます。enter、exit、および 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 と exit は対称である必要はありません。たとえば、フェードインしつつ方向性のあるスライドで退場させることができます:
<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 では、オブジェクトに default キーが必要です。
複数のページを持つアプリでは、タイプをキーとする VT を再利用可能なラッパーに切り出してください:
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() とブラウザーの戻るボタン
router.back() およびブラウザーの戻る/進むボタンには遷移タイプが付与されないため、タイプ別のアニメーション(方向性のあるスライド)は default に解決され、再生されません。タイプ付きのアニメーションを使うには、明示的な URL を指定して router.push() を使用してください。
タイプと Suspense
タイプはナビゲーション中は利用できますが、その後の Suspense による表示(別の遷移であり、タイプはありません)では利用できません。ページレベルの enter/exit にはタイプマップを使用し、Suspense による表示には単純な文字列の props を使用してください。
共有要素の準備状態
共有要素の遷移は、遷移元と遷移先の両方のビューが同じ Transition 内でレンダリングされている場合にのみ要素をペアリングできます。遷移先のコンテンツがサスペンドした場合、その更新ではフォールバックのみが存在し、解決されたコンテンツは後の Suspense 遷移で表示されるため、個別にアニメーションさせることができます。
共有要素遷移
同じ name を持つ 2 つの VT(一方はアンマウント、もう一方はマウント)は、共有要素のモーフィングを作成します:
<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>
- 特定の
nameを持つ VT は、同時に 1 つしかマウントできません。一意の名前(photo-${id})を使用してください。再利用可能なコンポーネントに注意してください。名前付き VT を持つコンポーネントがモーダル/ポップオーバーとページの両方でレンダリングされると、両方が同時にマウントされ、モーフィングが壊れます。名前を(props による)条件付きにするか、名前付き VT を共有コンポーネントから特定の利用側に移動してください。 shareはenter/exitより優先されます。各ナビゲーション経路を検討してください。一致するペアが形成されない場合(たとえば、遷移先のページに同じ名前がない場合)、代わりにenter/exitが発火します。それらの経路に要素のフォールバックアニメーションが必要かどうかを検討してください。- 設定済みのモーフィングが静かに発火しない場合が 2 つあります: (1) 明示的な
shareprops がなくdefault="none"の場合、share は none に解決されます。(2) タイプをキーとするshareで、ナビゲーションがそのタイプを追加していない場合、通常のリンククリックではマップのdefaultが解決されます。モーフィングすべきすべてのリンクはタイプを追加する必要があります(next/linkのtransitionTypes、またはaddTransitionType)。 - モーフィングを含むページでフェードアウトの exit を使用しないでください。代わりに方向性のあるスライドを使用してください。
よくあるパターン
Enter/Exit
{show && (
<ViewTransition enter="fade-in" exit="fade-out"><Panel /></ViewTransition>
)}
リストの並べ替え
{items.map(item => (
<ViewTransition key={item.id}><ItemCard item={item} /></ViewTransition>
))}
startTransition 内でトリガーしてください。リストと VT の間にラッパーの <div> を置かないでください。
レイアウト変位モーフ
有効化された境界内のコンテンツのみが位置をアニメーションし、それ以外はその新しいレイアウト位置にテレポートします。拡大/縮小するリストの下にある兄弟コンテンツを、ジャンプではなくスライドさせるために、素の <ViewTransition> で囲んでください。詳細は レイアウト変位モーフ を参照してください。
リスト ID を使った共有要素の組み合わせ
共有要素とリスト ID は独立した関心事であり、混同しないでください。リストアイテムに共有要素(例:詳細ビューへモーフィングする画像)が含まれる場合は、2 つの入れ子になった <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>
))}
外側の VT はリストの並べ替えや出現のアニメーションを担当します。内側の VT はルートをまたぐ共有要素のモーフィングを担当します。どちらか一方の層が欠けると、そのアニメーションは何のエラーもなく再生されません。
key による再出現の強制
<ViewTransition key={searchParams.toString()} enter="slide-up" default="none">
<ResultsGrid />
</ViewTransition>
注意: <Suspense> でラップしている場合、key を変更すると境界が再マウントされ、データが再取得されます。
Suspense のフォールバックからコンテンツへの切り替え
シンプルなクロスフェード:
<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.md を参照してください。
複数の VT の相互作用
トリガーに一致するすべての VT は、単一の document.startViewTransition 内で同時に発火します。異なるトランジション(ナビゲーションと、後から解決される Suspense)内の VT同士は競合しません。
default="none" を意図的に使用する
これを指定しないと、すべての VT が 毎回 ブラウザのクロスフェードを発火させます。Suspense の解決、useDeferredValue の更新、バックグラウンドの再検証などでも同様です。名前付き要素や型でキー付けされたページ VT には default="none" を使用してください。
ただし、これは update(レイアウトやリフローのモーフィング)と share(明示的な share プロップを持たない名前付きペアはモーフィングしない)も無効にします。キー付きのリストアイテムや押しのけられた兄弟要素は update を必要とするため、プロップを付けないか、update="auto" を設定してください。
2 つのパターンの共存
パターン A — 方向性のあるスライド: 各ページに型でキー付けされた VT を置き、ナビゲーション中に発火させます。 パターン B — Suspense のリビール: 単純な文字列プロップを使用し、データ読み込み時に発火させます(型は不要)。
両者は異なるタイミングで発火するため共存できます。両方に default="none" を指定すると、相互干渉を防げます。enter には必ず exit を組み合わせてください。方向性のある VT はレイアウトではなくページコンポーネントに配置してください。
入れ子の VT の制限
親 VT が、内部にネストされた VT を含んだまま 一体として マウント/アンマウントされる場合、ネストされた VT は独自の enter/exit を発火しません。外側の VT だけがアニメーションします。(永続的な親 VT の内側にマウントされた子 VT は、通常どおり enter/exit を発火します。)ページナビゲーション中のアイテムごとの段階的なアニメーションは、現在 Next.js では利用できません。上流の実験的ステータスについては、troubleshooting を参照してください。
Next.js との統合
Next.js との統合(next/link と useRouter の transitionTypes、App Router のパターン、Server Components)については、references/nextjs.md を参照してください。
アクセシビリティ
references/css-recipes.md の reduced motion 用 CSS を、必ずグローバルスタイルシートに追加してください。
リファレンスファイル
- references/implementation.md — 段階的な実装ワークフロー。
- references/patterns.md — パターン、アニメーションのタイミング、イベント API。
- references/troubleshooting.md — 症状に基づいたデバッグと実行時の制限事項。
- references/css-recipes.md — すぐに使える CSS アニメーションのレシピ。
- references/nextjs.md — Next.js App Router のパターンと Server Components の詳細。
完全なコンパイル済みドキュメント
すべてのリファレンスファイルを展開した完全なガイドは AGENTS.md を参照してください。