Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/aurora-drift/llms.txt

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

Aurora Drift wraps every page in a PageTransition component that coordinates a three-property entrance and exit animation: the page fades in, slides upward into view, and de-blurs simultaneously on mount — then reverses in the opposite vertical direction on unmount. The result is a continuous, cinematic flow between routes that reinforces the space-portfolio aesthetic. This is achieved with Framer Motion’s motion.div, AnimatePresence, and React Router’s useLocation hook to key transitions by route.

How AnimatePresence Works

Framer Motion’s AnimatePresence component monitors its direct children for unmounting. Normally when a React component unmounts (e.g., the old route’s page component), it disappears instantly. AnimatePresence intercepts the unmount, plays the exit animation to completion, and only then removes the element from the DOM. This is what makes the outgoing page animate out while the incoming page animates in. The critical requirement is that each child of AnimatePresence must have a unique key prop. When the key changes (i.e., the route changes), React treats it as a new element — the old one exits, the new one enters.
The y direction is intentional and asymmetric by design. On enter, the page starts at y: 20 (20px below its final position) and slides up into place. On exit, it starts at y: 0 and moves to y: -20 (20px above), sliding up and out. This creates a consistent upward flow — pages always travel upward through the viewport, like turning pages in the same direction.

Animation States

PageTransition defines three animation states on a single motion.div:
Stateopacityyfilter
initial (entering, before mount)020px (below)blur(10px)
animate (resting, on screen)10pxblur(0px)
exit (leaving, before unmount)0-20px (above)blur(10px)
The transition uses a custom cubic bezier easing — [0.22, 1, 0.36, 1] — which produces an ease-out-expo curve: the element accelerates sharply at the start of the animation and decelerates dramatically as it approaches its final position. This makes entrances feel snappy and decisive rather than mechanical.

The PageTransition Component

// components/PageTransition.js
import { motion } from 'framer-motion';

function PageTransition({ children }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20, filter: 'blur(10px)' }}
      animate={{ opacity: 1, y: 0,  filter: 'blur(0px)'  }}
      exit={{    opacity: 0, y: -20, filter: 'blur(10px)' }}
      transition={{
        duration: 0.6,
        ease: [0.22, 1, 0.36, 1], // ease-out-expo feel
      }}
      className="w-full h-full pt-24 pb-12 px-6 md:px-12 max-w-7xl mx-auto"
    >
      {children}
    </motion.div>
  );
}
The className on the wrapper does double duty — it applies both the animation and the page layout (pt-24 pb-12 px-6 md:px-12 max-w-7xl mx-auto), so every route automatically gets consistent padding and a centered max-width container.

Full Router Setup

Wiring PageTransition into React Router requires three things: getting the current location, passing it as a key to AnimatePresence’s child wrapper, and wrapping each route’s element in <PageTransition>.
import { Routes, Route, useLocation } from 'react-router-dom';
import { AnimatePresence } from 'framer-motion';
import { PageTransition } from './components/PageTransition';

import Home    from './pages/Home';
import About   from './pages/About';
import Work    from './pages/Work';
import Contact from './pages/Contact';

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

  return (
    <>
      <Nav />
      <AuroraBackground />
      <CursorGlow />

      {/*
        AnimatePresence must be outside <Routes> to detect
        when the route (and therefore the child key) changes.
        mode="wait" ensures the exit animation finishes before
        the next entrance begins — no overlap.
      */}
      <AnimatePresence mode="wait">
        {/*
          The key MUST be location.pathname (or location.key).
          When the route changes, React sees a new key and
          treats the child as a new element, triggering exit + enter.
        */}
        <Routes location={location} key={location.pathname}>
          <Route
            path="/"
            element={
              <PageTransition>
                <Home />
              </PageTransition>
            }
          />
          <Route
            path="/about"
            element={
              <PageTransition>
                <About />
              </PageTransition>
            }
          />
          <Route
            path="/work"
            element={
              <PageTransition>
                <Work />
              </PageTransition>
            }
          />
          <Route
            path="/contact"
            element={
              <PageTransition>
                <Contact />
              </PageTransition>
            }
          />
        </Routes>
      </AnimatePresence>
    </>
  );
}

Setting Up Page Transitions: Step by Step

1

Install dependencies

Ensure both framer-motion and react-router-dom are in your project.
npm install framer-motion react-router-dom
2

Wrap your app in BrowserRouter

useLocation must be called inside a Router context. Wrap your root component:
// main.jsx
import { BrowserRouter } from 'react-router-dom';

ReactDOM.createRoot(document.getElementById('root')).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);
3

Read the current location

In your App (or layout) component, call useLocation() to get the current route path:
const location = useLocation();
4

Add AnimatePresence with mode='wait'

Wrap your <Routes> in <AnimatePresence mode="wait">. Pass location and key={location.pathname} to <Routes>:
<AnimatePresence mode="wait">
  <Routes location={location} key={location.pathname}>
    {/* ... */}
  </Routes>
</AnimatePresence>
5

Wrap each route's element in PageTransition

Every route that should animate must have its element wrapped:
<Route path="/work" element={<PageTransition><Work /></PageTransition>} />

The Nav Underline: Layout Animation

The navigation bar uses a complementary animation for the active-route indicator. Rather than initial/animate/exit states, it uses Framer Motion’s layoutId to automatically animate the underline element from one nav item to the next when the active route changes:
// Nav.js (simplified)
function Nav() {
  const location = useLocation();

  const links = [
    { path: '/', label: 'Home' },
    { path: '/about', label: 'About' },
    { path: '/work', label: 'Work' },
    { path: '/contact', label: 'Contact' },
  ];

  return (
    <nav>
      {links.map(({ path, label }) => (
        <Link key={path} to={path} className="relative">
          {label}
          {location.pathname === path && (
            <motion.div
              layoutId="nav-underline"
              className="absolute bottom-0 left-0 right-0 h-0.5 bg-teal-400"
              transition={{ type: 'spring', stiffness: 300, damping: 30 }}
            />
          )}
        </Link>
      ))}
    </nav>
  );
}
When the active route changes, the motion.div with layoutId="nav-underline" smoothly slides from the old nav item’s position to the new one using a spring transition — no manual position calculations needed.

Customizing the Easing

The default Aurora Drift easing [0.22, 1, 0.36, 1] is an ease-out-expo curve. Here are alternatives to experiment with:
// Current: ease-out-expo — very fast start, gentle finish
ease: [0.22, 1, 0.36, 1]

// Ease-in-out — symmetric, gentle at both ends
ease: [0.4, 0, 0.2, 1]

// Ease-out-back — slight overshoot on arrival (bouncy entrance)
ease: [0.34, 1.56, 0.64, 1]

// Linear — constant speed, mechanical feel
ease: 'linear'

// Built-in Framer Motion named eases
ease: 'easeOut'
ease: 'anticipate'  // slight pullback before moving
You can also swap the ease transition for a spring to get physics-based page entrances, though this means accepting variable duration:
transition: { type: 'spring', stiffness: 80, damping: 20 }

AnimatePresence Mode Options

AnimatePresence has three mode options that control how concurrent exit and enter animations are handled:
ModeBehaviorBest for
"wait"Exit animation completes fully before the enter animation startsPage transitions — clean, no overlap
"sync"Exit and enter run simultaneously (default)Subtle crossfades, overlapping elements
"popLayout"Exiting element is removed from layout flow immediatelyTab panels, content that needs to reflow fast
Aurora Drift uses "wait" because the blur + slide transition looks best when the pages don’t overlap — having both blurred pages on screen simultaneously would be visually confusing. Use "sync" if you want a crossfade where both pages are briefly visible.

Transition Properties at a Glance

Enter

Starts 20px below final position with opacity: 0 and blur(10px). Eases upward and de-blurs over 0.6s using ease-out-expo.

Exit

Starts at final position and moves 20px upward (y: -20) while fading to opacity: 0 and re-blurring to blur(10px) over 0.6s.

Duration

0.6s total. Long enough to feel cinematic, short enough to not impede navigation. Adjust between 0.4s0.8s to taste.

Easing

[0.22, 1, 0.36, 1] — cubic bezier with steep initial acceleration and long gentle deceleration tail (ease-out-expo feel).

Build docs developers (and LLMs) love