Page Transition Wrapper
A dependency-free route transition shell with fade-through, shared-axis slide, scale and clip-reveal wipe modes. Direction-aware, interruption-safe, with focus moved to the new heading and a live route announcement.
DOM + CSS0 deps1 file · 10 KB TSXreduced-motion aware
Props
"use client";
import {
useCallback,
useEffect,
useRef,
useState,
useSyncExternalStore,
type CSSProperties,
type ReactNode,
} from "react";
/*
* PageTransitionWrapper: a dependency-free route transition shell.
*
* Give it a stable, unique `transitionKey` (the route pathname). Without one,
* unrelated pages are treated as the same page and the transition is skipped.
*
// ---------------------------------------------------------------
// Full source available with a viberdy Pro subscription.
// https://viberdy.dev/pricing
// ---------------------------------------------------------------
}Details
About this animation
Page Transition Wrapper is a route transition shell in plain React and CSS, no animation library. Give it a stable transitionKey (the pathname) and it keeps the outgoing page mounted in its own stacked layer while the new one enters. Four modes: fade (fade-through, the old page fades and scales down, then the new one arrives), slide (shared axis, both pages travel 40px along x), scale, and wipe (clip-reveal, the new page is uncovered by an animated clip-path edge while the old one recedes underneath). It is direction-aware: with direction auto, returning to the previous key plays in reverse (the slide flips sides, the wipe opens from the left); pass forward or back yourself when your router knows better. It is interruption-safe: layers are never recreated mid-flight, so clicking again simply retargets the running CSS transitions from wherever they are, and navigating back to a page that is still leaving revives that same layer. Only opacity, transform and clip-path animate. Notes: wipe reveals one page over another, so give the wrapper an opaque background (layers inherit it); layers sit in a one-cell grid so the wrapper is as tall as the tallest page; and the transitionKey must change for the route, not for every render.
Curator’s note
Rebuilt in October without framer-motion: the old fade, slide and scale props still work, and there is now a clip-reveal wipe, back-aware direction and focus management.

