Skip to content

Commit feebfc0

Browse files
authored
Add startViewTransition support (#10916)
1 parent f77743a commit feebfc0

30 files changed

+3003
-75
lines changed

.changeset/start-view-transition.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
"react-router-dom": minor
3+
"react-router": minor
4+
"@remix-run/router": minor
5+
---
6+
7+
Add support for the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition) via `document.startViewTransition` to enable CSS animated transitions on SPA navigations in your application.
8+
9+
The simplest approach to enabling a View Transition in your React Router app is via the new `<Link unstable_viewTransition>` prop. This will cause the navigation DOM update to be wrapped in `document.startViewTransition` which will enable transitions for the DOM update. Without any additional CSS styles, you'll get a basic cross-fade animation for your page.
10+
11+
If you need to apply more fine-grained styles for your animations, you can leverage the `unstable_useViewTransitionState` hook which will tell you when a transition is in progress and you can use that to apply classes or styles:
12+
13+
```jsx
14+
function ImageLink(to, src, alt) {
15+
let isTransitioning = unstable_useViewTransitionState(to);
16+
return (
17+
<Link to={to} unstable_viewTransition>
18+
<img
19+
src={src}
20+
alt={alt}
21+
style={{
22+
viewTransitionName: isTransitioning ? "image-expand" : "",
23+
}}
24+
/>
25+
</Link>
26+
);
27+
}
28+
```
29+
30+
You can also use the `<NavLink unstable_viewTransition>` shorthand which will manage the hook usage for you and automatically add a `transitioning` class to the `<a>` during the transition:
31+
32+
```css
33+
a.transitioning img {
34+
view-transition-name: "image-expand";
35+
}
36+
```
37+
38+
```jsx
39+
<NavLink to={to} unstable_viewTransition>
40+
<img src={src} alt={alt} />
41+
</NavLink>
42+
```
43+
44+
For an example usage of View Transitions with React Router, check out [our fork](https://github.com/brophdawg11/react-router-records) of the [Astro Records](https://github.com/Charca/astro-records) demo.
45+
46+
For more information on using the View Transitions API, please refer to the [Smooth and simple transitions with the View Transitions API](https://developer.chrome.com/docs/web-platform/view-transitions/) guide from the Google Chrome team.

docs/components/form.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ new: true
55

66
# `<Form>`
77

8+
<details>
9+
<summary>Type declaration</summary>
10+
11+
```tsx
12+
declare function Form(props: FormProps): React.ReactElement;
13+
14+
export interface LinkProps
15+
extends React.FormHTMLAttributes<HTMLFormElement> {
16+
method?: "get" | "post" | "put" | "patch" | "delete";
17+
encType?:
18+
| "application/x-www-form-urlencoded"
19+
| "multipart/form-data"
20+
| "text/plain";
21+
action?: string;
22+
relative?: "route" | "path";
23+
preventScrollReset?: boolean;
24+
onSubmit?: React.FormEventHandler<HTMLFormElement>;
25+
reloadDocument?: boolean;
26+
replace?: boolean;
27+
state?: any;
28+
unstable_viewTransition?: boolean;
29+
}
30+
31+
type To = string | Partial<Path>;
32+
33+
interface Path {
34+
pathname: string;
35+
search: string;
36+
hash: string;
37+
}
38+
```
39+
40+
</details>
41+
842
The Form component is a wrapper around a plain HTML [form][htmlform] that emulates the browser for client side routing and data mutations. It is _not_ a form validation/state management library like you might be used to in the React ecosystem (for that, we recommend the browser's built in [HTML Form Validation][formvalidation] and data validation on your backend server).
943

1044
<docs-warning>This feature only works if using a data router, see [Picking a Router][pickingarouter]</docs-warning>
@@ -243,6 +277,14 @@ If you are using [`<ScrollRestoration>`][scrollrestoration], this lets you preve
243277

244278
See also: [`<Link preventScrollReset>`][link-preventscrollreset]
245279

280+
## `unstable_viewTransition`
281+
282+
The `unstable_viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. If you need to apply specific styles for this view transition, you will also need to leverage the [`unstable_useViewTransitionState()`][use-view-transition-state].
283+
284+
<docs-warn>
285+
Please note that this API is marked unstable and may be subject to breaking changes without a major release.
286+
</docs-warn>
287+
246288
# Examples
247289

248290
TODO: More examples
@@ -349,3 +391,5 @@ You can access those values from the `request.url`
349391
[scrollrestoration]: ./scroll-restoration
350392
[link-preventscrollreset]: ./link#preventscrollreset
351393
[history-state]: https://developer.mozilla.org/en-US/docs/Web/API/History/state
394+
[use-view-transition-state]: ../hooks//use-view-transition-state
395+
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

docs/components/link.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ title: Link
1212
```tsx
1313
declare function Link(props: LinkProps): React.ReactElement;
1414

15-
interface LinkProps
15+
export interface LinkProps
1616
extends Omit<
1717
React.AnchorHTMLAttributes<HTMLAnchorElement>,
1818
"href"
@@ -23,6 +23,7 @@ interface LinkProps
2323
reloadDocument?: boolean;
2424
preventScrollReset?: boolean;
2525
relative?: "route" | "path";
26+
unstable_viewTransition?: boolean;
2627
}
2728

2829
type To = string | Partial<Path>;
@@ -146,8 +147,52 @@ let { state } = useLocation();
146147

147148
The `reloadDocument` property can be used to skip client side routing and let the browser handle the transition normally (as if it were an `<a href>`).
148149

150+
## `unstable_viewTransition`
151+
152+
The `unstable_viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`:
153+
154+
```jsx
155+
<Link to={to} unstable_viewTransition>
156+
```
157+
158+
If you need to apply specific styles for this view transition, you will also need to leverage the [`unstable_useViewTransitionState()`][use-view-transition-state]:
159+
160+
```jsx
161+
function ImageLink(to) {
162+
let isTransitioning = unstable_useViewTransitionState(to);
163+
return (
164+
<Link to={to} unstable_viewTransition>
165+
<p
166+
style={{
167+
viewTransitionName: isTransitioning
168+
? "image-title"
169+
: "",
170+
}}
171+
>
172+
Image Number {idx}
173+
</p>
174+
<img
175+
src={src}
176+
alt={`Img ${idx}`}
177+
style={{
178+
viewTransitionName: isTransitioning
179+
? "image-expand"
180+
: "",
181+
}}
182+
/>
183+
</Link>
184+
);
185+
}
186+
```
187+
188+
<docs-warn>
189+
Please note that this API is marked unstable and may be subject to breaking changes without a major release.
190+
</docs-warn>
191+
149192
[link-native]: ./link-native
150193
[scrollrestoration]: ./scroll-restoration
151194
[history-replace-state]: https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState
152195
[history-push-state]: https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
153196
[history-state]: https://developer.mozilla.org/en-US/docs/Web/API/History/state
197+
[use-view-transition-state]: ../hooks//use-view-transition-state
198+
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

docs/components/nav-link.md

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ title: NavLink
44

55
# `<NavLink>`
66

7-
A `<NavLink>` is a special kind of `<Link>` that knows whether or not it is "active" or "pending". This is useful when building a navigation menu, such as a breadcrumb or a set of tabs where you'd like to show which of them is currently selected. It also provides useful context for assistive technology like screen readers.
7+
A `<NavLink>` is a special kind of `<Link>` that knows whether or not it is "active", "pending", or "transitioning". This is useful in a few different scenarios:
8+
9+
- When building a navigation menu, such as a breadcrumb or a set of tabs where you'd like to show which of them is currently selected
10+
- It provides useful context for assistive technology like screen readers
11+
- It provides a "transitioning" value to give you finer-grained control over [View Transitions][view-transitions]
812

913
```tsx
1014
import { NavLink } from "react-router-dom";
@@ -42,8 +46,12 @@ The `className` prop works like a normal className, but you can also pass it a f
4246
```tsx
4347
<NavLink
4448
to="/messages"
45-
className={({ isActive, isPending }) =>
46-
isPending ? "pending" : isActive ? "active" : ""
49+
className={({ isActive, isPending, isTransitioning }) =>
50+
[
51+
isPending ? "pending" : "",
52+
isActive ? "active" : "",
53+
isTransitioning ? "transitioning" : "",
54+
].join(" ")
4755
}
4856
>
4957
Messages
@@ -57,10 +65,11 @@ The `style` prop works like a normal style prop, but you can also pass it a func
5765
```tsx
5866
<NavLink
5967
to="/messages"
60-
style={({ isActive, isPending }) => {
68+
style={({ isActive, isPending, isTransitioning }) => {
6169
return {
6270
fontWeight: isActive ? "bold" : "",
6371
color: isPending ? "red" : "black",
72+
viewTransitionName: isTransitioning ? "slide" : "",
6473
};
6574
}}
6675
>
@@ -74,7 +83,7 @@ You can pass a render prop as children to customize the content of the `<NavLink
7483

7584
```tsx
7685
<NavLink to="/tasks">
77-
{({ isActive, isPending }) => (
86+
{({ isActive, isPending, isTransitioning }) => (
7887
<span className={isActive ? "active" : ""}>Tasks</span>
7988
)}
8089
</NavLink>
@@ -112,4 +121,60 @@ When a `NavLink` is active it will automatically apply `<a aria-current="page">`
112121

113122
The `reloadDocument` property can be used to skip client side routing and let the browser handle the transition normally (as if it were an `<a href>`).
114123

124+
## `unstable_viewTransition`
125+
126+
The `unstable_viewTransition` prop enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. By default, during the transition a `transitioning` class will be added to the `<a>` element that you can use to customize the view transition.
127+
128+
```css
129+
a.transitioning p {
130+
view-transition-name: "image-title";
131+
}
132+
133+
a.transitioning img {
134+
view-transition-name: "image-expand";
135+
}
136+
```
137+
138+
```jsx
139+
<NavLink to={to} unstable_viewTransition>
140+
<p>Image Number {idx}</p>
141+
<img src={src} alt={`Img ${idx}`} />
142+
</NavLink>
143+
```
144+
145+
You may also use the `className`/`style` props or the render props passed to `children` to further customize based on the `isTransitioning` value.
146+
147+
```jsx
148+
<NavLink to={to} unstable_viewTransition>
149+
{({ isTransitioning }) => (
150+
<>
151+
<p
152+
style={{
153+
viewTransitionName: isTransitioning
154+
? "image-title"
155+
: "",
156+
}}
157+
>
158+
Image Number {idx}
159+
</p>
160+
<img
161+
src={src}
162+
alt={`Img ${idx}`}
163+
style={{
164+
viewTransitionName: isTransitioning
165+
? "image-expand"
166+
: "",
167+
}}
168+
/>
169+
</>
170+
)}
171+
</NavLink>
172+
```
173+
174+
<docs-warn>
175+
Please note that this API is marked unstable and may be subject to breaking changes without a major release.
176+
</docs-warn>
177+
115178
[aria-current]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current
179+
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
180+
[use-view-transition-state]: ../hooks//use-view-transition-state

docs/hooks/use-navigate.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,19 @@ function EditContact() {
8585
}
8686
```
8787

88+
## `options.unstable_viewTransition`
89+
90+
The `unstable_viewTransition` option enables a [View Transition][view-transitions] for this navigation by wrapping the final state update in `document.startViewTransition()`. If you need to apply specific styles for this view transition, you will also need to leverage the [`unstable_useViewTransitionState()`][use-view-transition-state].
91+
92+
<docs-warn>
93+
Please note that this API is marked unstable and may be subject to breaking changes without a major release.
94+
</docs-warn>
95+
8896
[link]: ../components/link
8997
[redirect]: ../fetch/redirect
9098
[loaders]: ../route/loader
9199
[actions]: ../route/action
92100
[history-state]: https://developer.mozilla.org/en-US/docs/Web/API/History/state
93101
[scrollrestoration]: ../components/scroll-restoration
102+
[use-view-transition-state]: ../hooks//use-view-transition-state
103+
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

docs/hooks/use-submit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ submit(null, {
150150
<Form action="/logout" method="post" />;
151151
```
152152

153-
Because submissions are navigations, the options may also contain the other navigation related props from [`<Form>`][form] such as `replace`, `state`, `preventScrollReset`, `relative`, etc.
153+
Because submissions are navigations, the options may also contain the other navigation related props from [`<Form>`][form] such as `replace`, `state`, `preventScrollReset`, `relative`, `unstable_viewTransition` etc.
154154

155155
[pickingarouter]: ../routers/picking-a-router
156156
[form]: ../components/form
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: unstable_useViewTransitionState
3+
---
4+
5+
# `unstable_useViewTransitionState`
6+
7+
<details>
8+
<summary>Type declaration</summary>
9+
10+
```tsx
11+
declare function unstable_useViewTransitionState(
12+
to: To,
13+
opts: { relative?: "route" : "path" } = {}
14+
): boolean;
15+
16+
type To = string | Partial<Path>;
17+
18+
interface Path {
19+
pathname: string;
20+
search: string;
21+
hash: string;
22+
}
23+
```
24+
25+
</details>
26+
27+
This hook returns `true` when there is an active [View Transition][view-transitions] to the specified location. This can be used to apply finer-grained styles to elements to further customize the view transition. This requires that view transitions have been enabled for the given navigation via the [unstable_viewTransition][link-view-transition] prop on the `Link` (or the `Form`, `navigate`, or `submit` call).
28+
29+
Consider clicking on an image in a list that you need to expand into the hero image on the destination page:
30+
31+
```jsx
32+
function NavImage({ src, alt, id }) {
33+
let to = `/images/${idx}`;
34+
let vt = unstable_useViewTransitionState(href);
35+
return (
36+
<Link to={to} unstable_viewTransition>
37+
<img
38+
src={src}
39+
alt={alt}
40+
style={{
41+
viewTransitionName: vt ? "image-expand" : "",
42+
}}
43+
/>
44+
</Link>
45+
);
46+
}
47+
```
48+
49+
[link-view-transition]: ../components/link#unstable_viewtransition
50+
[view-transitions]: https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API

examples/view-transitions/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"installDependencies": true,
3+
"startCommand": "npm run dev"
4+
}

examples/view-transitions/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: View Transitions
3+
toc: false
4+
---
5+
6+
# startViewTransition (Experimental)
7+
8+
This example demonstrates a simple usage of a Data Router with `document.startViewTransition` enabled.
9+
10+
## Preview
11+
12+
Open this example on [StackBlitz](https://stackblitz.com):
13+
14+
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router/tree/brophdawg11/start-view-transition/examples/view-transitions?file=src/App.tsx)

0 commit comments

Comments
 (0)