docs / core / page transition
Page Transition
a route-level fade and lift: content enters from below and exits softly upward, with the outgoing view finishing before the next mounts. framework-neutral, keyed on whatever routeKey you hand it.
intensity · guidesrsc · client
provenance
authored in usvalayer
core / motionintensity
guides · leads the eye through a sequence, then clearscomposition
wraps the route outlet in the app shell, onceany router works, key it on the pathnamenever nested. one transition per shellnot for in-page swaps like tabs or lists, it unmounts everything under ita11y
returns children untouched underprefers-reduced-motion · the wrapper is a plain div, no roles, no focus trappingjest-axedependencies
motionlive · simulated routes
users2,412uptime99.9%builds18
the summary at a glance. switch tabs and the whole view lifts out while the next one fades up from below.
props
| prop | type | default | notes |
|---|---|---|---|
| routeKey | string | — | the current route. usePathname() in next, location.pathname anywhere else. a change fires the transition. |
| children | ReactNode | — | the route content. the whole subtree unmounts and remounts on every key change. |
get it
npx shadcn add https://usva.build/r/page-transition.jsonsource · components/ui/page-transition.tsxexactly what this command copies
"use client";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import type * as React from "react";
export interface PageTransitionProps {
/** Change this per route (e.g. the pathname) to trigger the transition. */
routeKey: string;
children: React.ReactNode;
}
/**
* Framework-neutral route transition: fade and lift in, soft lift out. Pass the
* current route as `routeKey` (e.g. `usePathname()` in Next, `location.pathname`
* elsewhere). Collapses to a plain render under reduced motion.
*/
export function PageTransition({ routeKey, children }: PageTransitionProps) {
const reduced = useReducedMotion();
if (reduced) return <>{children}</>;
return (
<AnimatePresence mode="wait">
<motion.div
key={routeKey}
initial={{ opacity: 0, y: 20 }}
animate={{
opacity: 1,
y: 0,
transition: { duration: 0.38, ease: [0.22, 1, 0.36, 1] },
}}
exit={{
opacity: 0,
y: -12,
transition: { duration: 0.22, ease: [0.4, 0, 1, 1] },
}}
>
{children}
</motion.div>
</AnimatePresence>
);
}