Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/webmaster/llms.txt

Use this file to discover all available pages before exploring further.

PageTransition is a thin wrapper around Framer Motion’s motion.div that gives every route change a smooth fade-and-slide feel. When a new page enters, it slides up from 20px below, fades in, and scales from 0.98 to full size. When a page exits, it slides up a further 20px, fades out, and scales back down slightly. The AnimatePresence mode="wait" at the router level ensures the outgoing page finishes its exit animation before the incoming page begins — preventing any visual overlap between routes.

Props

children
React.ReactNode
required
The page component to animate. Wrap each route’s element with <PageTransition> so every route gets its own motion.div lifecycle.

Animation Values

The component passes the following variant objects directly to motion.div:
PropValueDescription
initial{ opacity: 0, y: 20, scale: 0.98 }Page starts invisible, offset 20px below and very slightly scaled down
animate{ opacity: 1, y: 0, scale: 1 }Page fades in, slides up to its natural position, and scales to full size
exit{ opacity: 0, y: -20, scale: 0.98 }Page fades out while continuing to slide upward and shrinking slightly
transition{ duration: 0.4, ease: 'easeOut' }400ms ease-out on both enter and exit
The symmetry between y: 20 on enter and y: -20 on exit creates a continuous upward momentum — each page flows naturally out of the one before it.

Core Implementation

import { motion } from 'framer-motion';

export function PageTransition({ children }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20, scale: 0.98 }}
      animate={{ opacity: 1, y: 0, scale: 1 }}
      exit={{ opacity: 0, y: -20, scale: 0.98 }}
      transition={{ duration: 0.4, ease: 'easeOut' }}
      className="w-full h-full scanlines"
    >
      {children}
    </motion.div>
  );
}

Router-Level Setup

AnimatePresence must wrap the <Routes> block, not individual components. Setting mode="wait" tells Framer Motion to complete the current page’s exit animation before mounting the next page’s initial state — without this, both pages would be in the DOM simultaneously and the transitions would overlap. The key={location.pathname} on <Routes> is essential: it signals to AnimatePresence that the child tree has changed when the URL changes, triggering the exit/enter lifecycle.
import { AnimatePresence } from 'framer-motion';
import { Routes, Route, useLocation } from 'react-router-dom';
import { PageTransition } from './components/PageTransition';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Projects } from './pages/Projects';
import { Contact } from './pages/Contact';

export function App() {
  const location = useLocation();

  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/"        element={<PageTransition><Home /></PageTransition>} />
        <Route path="/about"   element={<PageTransition><About /></PageTransition>} />
        <Route path="/projects" element={<PageTransition><Projects /></PageTransition>} />
        <Route path="/contact" element={<PageTransition><Contact /></PageTransition>} />
      </Routes>
    </AnimatePresence>
  );
}
The location prop on <Routes> and the matching key are both required. If you omit key={location.pathname}, React will reuse the same Routes element between navigations and AnimatePresence will never see a child unmount — meaning exit animations will never fire.

Dependency

PageTransition requires Framer Motion, which is already installed in the Webmaster project:
{
  "dependencies": {
    "framer-motion": "^11.x"
  }
}
No additional configuration is needed. Framer Motion’s motion components and AnimatePresence are imported directly from the framer-motion package.

How AnimatePresence mode="wait" Works

1

Navigation triggered

The user clicks a nav link. React Router updates location.pathname.
2

Key change detected

The key={location.pathname} on <Routes> changes, so React unmounts the old Routes tree and mounts a new one. AnimatePresence intercepts the unmount.
3

Exit animation plays

The outgoing page’s motion.div animates to { opacity: 0, y: -20, scale: 0.98 } over 400ms. The element stays in the DOM until the animation completes.
4

New page mounts

Once the exit animation is done, AnimatePresence allows the old element to fully unmount, then the new page mounts and animates from { opacity: 0, y: 20, scale: 0.98 } to { opacity: 1, y: 0, scale: 1 }.
To adjust the feel of transitions, tweak the duration (currently 0.4s) or swap ease: 'easeOut' for another Framer Motion easing like 'anticipate' or a custom cubic-bezier array such as [0.4, 0, 0.2, 1].

Build docs developers (and LLMs) love