UNPKG

9.54 kB Markdown View Raw
1---
2title: View Transitions
3---
4
5# View Transitions
6
7[MODES: framework, data, declarative]
8
9Use React's [`<ViewTransition>`][react-view-transition] component to animate client-side navigations. This requires React and React DOM 19.3 or later.
10
11<docs-warning>
12
13React Router's `viewTransition` props/options, `useViewTransitionState` hook, and `NavLink`'s `isTransitioning` render prop and `transitioning` class are deprecated in favor of React's `<ViewTransition>` component. The existing APIs continue to work, including with older React versions.
14
15</docs-warning>
16
17## Navigation View Transitions with React
18
19Wrap the outlet in a persistent `<ViewTransition>` boundary in a shared layout route. In Framework Mode, this can be your root route's default export; in Data and Declarative modes, use it as the component for a parent route with children:
20
21```tsx
22import { ViewTransition } from "react";
23import { Link, Outlet } from "react-router";
24
25export default function PageLayout() {
26 return (
27 <>
28 <nav>
29 <Link to="/">Home</Link>
30 <Link to="/about">About</Link>
31 </nav>
32 <ViewTransition>
33 <main>
34 <Outlet />
35 </main>
36 </ViewTransition>
37 </>
38 );
39}
40```
41
42Navigations that update the outlet cross-fade its contents. Keep the boundary mounted across navigations; you don't need to key it by location or remount the route tree. The navigation stays outside the animated region. Other updates inside the boundary can also animate when they run in a React Transition.
43
44React Router wraps router state updates in [`React.startTransition`][start-transition] by default, which lets React activate `<ViewTransition>` without additional configuration. React Transitions must remain enabled for navigation view transitions to work: do not set `useTransitions={false}` on your router.
45
46This applies to navigations from `<Link>`, `<NavLink>`, and `<Form>`, as well as programmatic navigation with `useNavigate` and `useSubmit`. No additional `startTransition` wrapper is needed.
47
48### Migrating from React Router's view transitions
49
50- Remove `viewTransition` from `<Link>`, `<NavLink>`, and `<Form>`, and remove `viewTransition: true` from navigation/submission options. React coordinates `document.startViewTransition()` itself; enabling both implementations can interrupt animations.
51- Replace `useViewTransitionState`, `isTransitioning`, and `.transitioning` styles with React's [View Transition classes][react-view-transition-classes]. Those router APIs only track the legacy transitions.
52- For shared elements such as an image moving from a list to a detail route, wrap each side in `<ViewTransition name="...">` with the same unique name. See React's [shared element example][react-shared-elements].
53- Keep React Transitions enabled. `useTransitions={false}` and synchronous updates such as `flushSync` can prevent React's animations.
54
55React's component is not a drop-in replacement for every legacy animation. In particular, React [skips view transitions for synchronous updates from `popstate`][react-router-transitions], so browser Back/Forward navigations may not animate. Browsers without View Transitions support still navigate normally.
56
57Respect reduced-motion preferences when adding animations:
58
59```css
60@media (prefers-reduced-motion: reduce) {
61 ::view-transition-group(*),
62 ::view-transition-old(*),
63 ::view-transition-new(*) {
64 animation: none !important;
65 }
66}
67```
68
69## Legacy View Transitions (Deprecated)
70
71The following examples document the deprecated APIs for existing applications. These APIs are available in Framework and Data modes.
72
73### Basic View Transition
74
75#### 1. Enable view transitions on navigation
76
77The simplest way to enable view transitions is by adding the `viewTransition` prop to your `Link`, `NavLink`, or `Form` components. This automatically wraps the navigation update in `document.startViewTransition()`.
78
79```tsx
80<Link to="/about" viewTransition>
81 About
82</Link>
83```
84
85Without any additional CSS, this provides a basic cross-fade animation between pages.
86
87#### 2. Enable view transitions with programmatic navigation
88
89When using programmatic navigation with the `useNavigate` hook, you can enable view transitions by passing the `viewTransition: true` option:
90
91```tsx
92import { useNavigate } from "react-router";
93
94function NavigationButton() {
95 const navigate = useNavigate();
96
97 return (
98 <button
99 onClick={() =>
100 navigate("/about", { viewTransition: true })
101 }
102 >
103 About
104 </button>
105 );
106}
107```
108
109This provides the same cross-fade animation as using the `viewTransition` prop on Link components.
110
111For more information on using the View Transitions API, please refer to the ["Smooth transitions with the View Transition API" guide][view-transitions-guide] from the Google Chrome team.
112
113### Image Gallery Example
114
115Let's build an image gallery that demonstrates how to trigger and use view transitions. We'll create a list of images that expand into a detail view with smooth animations.
116
117#### 1. Create the image gallery route
118
119```tsx filename=routes/image-gallery.tsx
120import { NavLink } from "react-router";
121
122export const images = [
123 "https://remix.run/blog-images/headers/the-future-is-now.jpg",
124 "https://remix.run/blog-images/headers/waterfall.jpg",
125 "https://remix.run/blog-images/headers/webpack.png",
126 // ... more images ...
127];
128
129export default function ImageGalleryRoute() {
130 return (
131 <div className="image-list">
132 <h1>Image List</h1>
133 <div>
134 {images.map((src, idx) => (
135 <NavLink
136 key={src}
137 to={`/image/${idx}`}
138 viewTransition // Enable view transitions for this link
139 >
140 <p>Image Number {idx}</p>
141 <img
142 className="max-w-full contain-layout"
143 src={src}
144 />
145 </NavLink>
146 ))}
147 </div>
148 </div>
149 );
150}
151```
152
153#### 2. Add transition styles
154
155Define view transition names and animations for elements that should transition smoothly between routes.
156
157```css filename=app.css
158/* Layout styles for the image grid */
159.image-list > div {
160 display: grid;
161 grid-template-columns: repeat(4, 1fr);
162 column-gap: 10px;
163}
164
165.image-list h1 {
166 font-size: 2rem;
167 font-weight: 600;
168}
169
170.image-list img {
171 max-width: 100%;
172 contain: layout;
173}
174
175.image-list p {
176 width: fit-content;
177}
178
179/* Assign transition names to elements during navigation */
180.image-list a.transitioning img {
181 view-transition-name: image-expand;
182}
183
184.image-list a.transitioning p {
185 view-transition-name: image-title;
186}
187```
188
189#### 3. Create the image detail route
190
191The detail view needs to use the same view transition names to create a seamless animation.
192
193```tsx filename=routes/image-details.tsx
194import { Link } from "react-router";
195import { images } from "./home";
196import type { Route } from "./+types/image-details";
197
198export default function ImageDetailsRoute({
199 params,
200}: Route.ComponentProps) {
201 return (
202 <div className="image-detail">
203 <Link to="/" viewTransition>
204 Back
205 </Link>
206 <h1>Image Number {params.id}</h1>
207 <img src={images[Number(params.id)]} />
208 </div>
209 );
210}
211```
212
213#### 4. Add matching transition styles for the detail view
214
215```css filename=app.css
216/* Match transition names from the list view */
217.image-detail h1 {
218 font-size: 2rem;
219 font-weight: 600;
220 width: fit-content;
221 view-transition-name: image-title;
222}
223
224.image-detail img {
225 max-width: 100%;
226 contain: layout;
227 view-transition-name: image-expand;
228}
229```
230
231### Advanced Usage
232
233You can control view transitions more precisely using either render props or the `useViewTransitionState` hook.
234
235#### 1. Using render props
236
237```tsx filename=routes/image-gallery.tsx
238<NavLink to={`/image/${idx}`} viewTransition>
239 {({ isTransitioning }) => (
240 <>
241 <p
242 style={{
243 viewTransitionName: isTransitioning
244 ? "image-title"
245 : "none",
246 }}
247 >
248 Image Number {idx}
249 </p>
250 <img
251 src={src}
252 style={{
253 viewTransitionName: isTransitioning
254 ? "image-expand"
255 : "none",
256 }}
257 />
258 </>
259 )}
260</NavLink>
261```
262
263#### 2. Using the `useViewTransitionState` hook
264
265```tsx filename=routes/image-gallery.tsx
266function NavImage(props: { src: string; idx: number }) {
267 const href = `/image/${props.idx}`;
268 // Hook provides transition state for specific route
269 const isTransitioning = useViewTransitionState(href);
270
271 return (
272 <Link to={href} viewTransition>
273 <p
274 style={{
275 viewTransitionName: isTransitioning
276 ? "image-title"
277 : "none",
278 }}
279 >
280 Image Number {props.idx}
281 </p>
282 <img
283 src={props.src}
284 style={{
285 viewTransitionName: isTransitioning
286 ? "image-expand"
287 : "none",
288 }}
289 />
290 </Link>
291 );
292}
293```
294
295[view-transitions-api]: https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition
296[react-view-transition]: https://react.dev/reference/react/ViewTransition
297[start-transition]: https://react.dev/reference/react/startTransition
298[react-view-transition-classes]: https://react.dev/reference/react/ViewTransition#view-transition-class
299[react-shared-elements]: https://react.dev/reference/react/ViewTransition#animating-a-shared-element
300[react-router-transitions]: https://react.dev/reference/react/ViewTransition#building-view-transition-enabled-routers
301[view-transitions-guide]: https://developer.chrome.com/docs/web-platform/view-transitions