Stockio

React Page Transitions With Motion (Framer Motion)

Add React route transitions with Motion using stable presence boundaries, deliberate route keys, reduced-motion variants, and explicit focus, scroll, and loading policies.

By Sofia Marchetti

JavaScript Engineer ·

On this page

A route transition connects two navigation states; it should not conceal a slow request or delay access to the next page. In React, the difficult part is usually not the easing. It is keeping the outgoing view alive long enough to exit while the router prepares the incoming one.

Separate route layout from animated content

Keep persistent navigation, global notifications, and shared application chrome outside the animated page region. Moving the entire application can make every navigation feel like a restart and can disturb controls that should remain stable.

Use the CSS to Tailwind converter for static wrapper styles if your team uses utilities. Translate layout, spacing, and positioning separately from Motion's runtime transforms. Do not let a utility transform and Motion both compete to own the same element's transform property.

Define a navigation contract first: which route changes animate, whether search parameters count, where focus moves, and how scroll is restored. The micro-interaction guide helps frame the transition as orientation rather than spectacle.

The current package uses imports from motion/react. Follow the official React installation guide for your setup rather than mixing older package examples with newer APIs. In server-component frameworks, the component using these hooks and animation primitives needs the appropriate client boundary.

Keep the presence boundary mounted

AnimatePresence observes keyed children being removed and allows their exit animations to finish. It cannot perform that job if its own parent disappears at the same moment. Place the presence boundary in a stable part of the route layout.

The example below accepts a resolved route view and a deliberate route identity. It is a transition boundary, not a router implementation. The router adapter must supply the correct identity and view together, without letting the exiting view suddenly render the incoming route's content.

import type { ReactNode } from "react";
import {
AnimatePresence,
motion,
useReducedMotion,
} from "motion/react";

type RouteFrameProps = {
routeId: string;
children: ReactNode;
};

export function RouteFrame({ routeId, children }: RouteFrameProps) {
const reduce = useReducedMotion();

return (
  <AnimatePresence initial={false} mode="wait">
    <motion.div
      key={routeId}
      initial={reduce ? false : { opacity: 0, y: 8 }}
      animate={{ opacity: 1, y: 0 }}
      exit={reduce ? { opacity: 1 } : { opacity: 0, y: -4 }}
      transition={{ duration: reduce ? 0 : 0.16 }}
    >
      {children}
    </motion.div>
  </AnimatePresence>
);
}

Use one child for wait mode. It sequences exit before entrance, which avoids overlapping documents but adds the exit time to the wait before the next view appears. For frequent navigation, simultaneous movement or a simple entrance-only fade may be more responsive.

Setting initial to false on AnimatePresence avoids animating the initial page load through this boundary. The reduced-motion branch removes displacement and makes the transition immediate. See the AnimatePresence documentation for presence behavior and mode constraints.

Choose route identity deliberately

A key determines whether React treats a view as the same instance or a replacement. Using a pathname is a reasonable starting point for distinct screens, but it is not universally correct. Two URLs can represent the same screen with different filters, or the same pathname can contain a meaningful parameterized destination.

Choose stable route keys based on the navigation model. If changing a sort query should preserve the page instance, exclude that query from the key. If opening another document should create a new page state, include the document identity. Avoid random keys or timestamps, which force unnecessary remounts.

Remember that remounting also resets local state. A transition that looks correct can silently clear a draft or restart an expensive embedded viewer. Review React's state preservation guide before treating keys as animation-only settings.

Router integration varies. Some routers expose a location snapshot you can pass to route rendering; others control nested layouts and replacement boundaries internally. Verify that the old keyed child retains its old route output during exit. A wrapper around a live router outlet does not guarantee that behavior.

Nested routes usually need local transitions rather than a single root animation. Keep a project sidebar mounted while its detail panel changes. This reduces unnecessary remounting and gives users a stable spatial reference during deeper navigation.

Coordinate loading, focus, and scroll

Decide whether exit begins on navigation intent or after the destination is ready. Starting immediately can leave a blank interval during a slow fetch. Waiting for data preserves the current screen longer but needs an honest pending indicator so the application does not appear unresponsive.

Do not use a long animation to disguise loading. Prefer route-level loading states or retained content while the router works. A short transition should connect ready states, not impose a theatrical delay on top of network latency.

Manage focus ownership explicitly. When the outgoing page contains focus, remove its interactive descendants from the active interaction path during exit, and move focus to the incoming page's heading or main region once it is mounted and ready. Avoid focusing a destination element before it exists.

Wait mode alone does not make the outgoing content inert. An exiting link can still be clicked unless you manage interaction. Pointer-events rules address pointer input only; inert or an equivalent focus strategy is needed for keyboard interaction too.

Scroll restoration belongs to navigation policy. A new destination may start at the top, while back navigation should often restore a previous position. Do not unconditionally scroll on every render or search-parameter update. Pair scroll changes with the router's restoration mechanism and test anchors separately.

Use the accessible interface guide to review landmarks and focus visibility. Avoid duplicate main landmarks if you choose an overlapping transition; the temporary outgoing page must not remain a second active document for assistive technology.

Handle interruption and measure the result

Users can navigate again before an exit finishes. Test double clicks, back and forward navigation, cancelled requests, and rapidly changing parameters. The final visible route must match the current URL, not whichever animation callback completed last.

Use reduced motion as a behavior policy, not just a shorter duration. The hook in the example removes spatial movement entirely. If you add shared-element or large-scale transitions later, apply the same preference consistently across those additional effects.

Profile real pages rather than empty colored rectangles. Charts, video players, and large lists can make remounting far more expensive than the transition itself. Keep expensive shared elements outside the replaced boundary when their state should persist.

Record the complete navigation with a slow network and a slower CPU. Inspect the gap between click, pending feedback, destination readiness, and focus arrival. The motion should clarify that sequence, not become its longest stage. A restrained fade is often a better product decision than a complex animation requiring fragile router coordination.

Key takeaways

  • Keep AnimatePresence mounted above the changing route view.
  • Choose keys for navigation identity, not merely visual replay.
  • Verify that exiting children retain their original route content.
  • Coordinate focus, inertness, loading, and scroll outside the animation API.
  • Test interrupted navigation and offer immediate reduced-motion transitions.
Featured sponsor

Reach designers & developers at work

Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.

Become a sponsor

Keep reading

Related articles

More in JavaScript & Frameworks
JavaScript & Frameworks

12 Best React Libraries for Everyday Projects

Find practical React libraries for routing, forms, data, animation, and interface building. These approachable picks explain what each adds and where a smaller dependency may be wiser.

Includes: CSS to TailwindSofia MarchettiSofia Marchetti
Design Systems

10 Best Tailwind CSS Component Libraries

Find a Tailwind component library for landing pages, dashboards, or custom products, with ten options explained through their real strengths, trade-offs, and ideal use cases.

Includes: CSS to TailwindMaya LindqvistMaya Lindqvist
Design Systems

10 Best React UI Component Libraries

Compare ten established React component libraries for dashboards, accessible forms, and polished apps, with practical strengths, limitations, and advice for choosing a sensible fit.

Includes: CSS to TailwindJonah WeissJonah Weiss
HTML & CSS

10 Best CSS Frameworks

Compare ten popular CSS frameworks, from utility classes to ready-made components, and find an approach that fits your team's skills, design needs, and project size.

Includes: CSS to TailwindMarcus BellMarcus Bell
AI Builders & APIs

v0 Tutorial: Generate React Components and Ship Them

Turn v0-generated React interfaces into production-ready components with clear prop contracts, design-system adaptation, accessibility checks and focused integration tests.

Includes: CSS to TailwindDaniel KimDaniel Kim
Dev Tools & AI Coding

Generate TypeScript Types From JSON and API Data With AI

Generate TypeScript contracts from representative API responses, model nullable fields and variants accurately, and add runtime checks before data reaches your interface.

Includes: CSS to TailwindRavi DesaiRavi Desai