vercel-react-view-transitions について
既存アプリに React View Transitions を導入するための、UI アニメーション向けガイドスキルです。遷移が伝える関係に基づく選び方や、React と Next.js での実装、制約を説明します。
主な機能
- 01共有要素とリストの遷移
- 02Suspense と状態変化のアニメーション
- 03型付きナビゲーション遷移
- 04CSS アニメーションレシピと資料
- 05動きを減らすアクセシビリティ指針
活用シーン
- 01既存の React アプリへの遷移追加
- 02一覧から詳細への移動のアニメーション
- 03Suspense 読み込み後のコンテンツ表示
- 04遷移の配置と動作の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ fcakyon/ claude-codex-settings --skill vercel-react-view-transitionsインストール後、Claude Code で/vercel-react-view-transitionsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/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@canary react-dom@canaryをインストールしてください (ViewTransitionは安定版 React には含まれていません)。 - ブラウザサポート: 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 | <ViewTransition> がトランジション中に初めて挿入されたとき |
| exit | <ViewTransition> がトランジション中に初めて削除されたとき |
| update | <ViewTransition> 内の DOM 変更、または直接の兄弟要素が原因で境界自体のサイズ/位置が変わったとき。ネストされた VT の場合、変更は最も内側のものに適用される |
| share | 名前付き VT がアンマウントされ、同じ name を持つ別の VT が同じトランジション内でマウントされたとき |
startTransition、useDeferredValue、または Suspense だけが VT を有効化します。通常の 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>
ビュートランジションクラスによるスタイリング
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 を参照してください。