
When you navigate between pages on high-end editorial portfolios, the transition rarely feels like a traditional webpage loading. Instead of a jarring hard-cut or an uninspired opacity crossfade, the outgoing page physically retreats — shrinking down, drifting south, and tilting slightly off-axis — while the incoming view sweeps across from the bottom-right corner through an angled, expanding diagonal slice.
It creates a tactile sensation of depth and physical momentum, reminiscent of sliding photography boards across an architect's desk.
We recently packaged this transition into two complementary forms in the Sora ecosystem:
- The Component Pattern:
TiltSlideTransition— an interactive, self-contained layout showcase with GSAP SplitText character and line reveals, Lenis smooth scrolling, and controlled view switching. - The App Router Architecture:
tilt-slide-template— a standalone, multi-page Next.js template using React 19's native<ViewTransition>component intemplate.tsx.
Here is the complete teardown of how the animation works, the mathematics of the CSS keyframes, and how to implement it both as a contained component and across real multi-page Next.js routes.
What makes the transition feel so natural?
The secret to this effect isn't just movement; it's choreographed asymmetry.
Most multi-page transitions apply symmetric easing: Page A slides left by 100%, Page B enters from the right by 100% at the exact same velocity. The result often feels mechanical.
In the tilt-slide transition, two distinct spatial transformations happen on an offset timeline:
| Transition Layer | Start Time | Duration | Key Motion Behavior |
|---|---|---|---|
Outgoing (page-out) | 0.00s | 1600ms | Scales down to 0.8 → tilts -10° → drifts down-left (-50%, 20%) |
Incoming (page-in) | +0.25s (delay) | 1500ms | Expands diagonal clip-path from bottom-right → slides to (0, 0) |
| Navbar (Anchor) | Continuous | Persistent | Remains fixed and un-animated via view-transition-name: navbar |
The 3-Phase Choreography
-
Phase 1: Depth Squeeze (
0.00s → 0.25s)
The active page scales down to80%while staying level (rotate(0deg)). This gives the viewer's eye an instant cue that the current plane is receding in 3D space. -
Phase 2: The Offset Slice (
+0.25s)
After a deliberate 250ms delay, the incoming page cuts in from the bottom-right quadrant using an angled diagonalclip-path. -
Phase 3: Tilt & Full Reveal (
0.25s → 1.75s)
While the outgoing page tilts-10°and slides out of frame, the new page expands to 100% viewport coverage and settles cleanly into place.
Deep Dive: The CSS Keyframe Mechanics
All the motion lives in standard CSS pseudo-elements provided by the browser's View Transition API:
The Outgoing Squeeze & Tilt
Notice the midpoint keyframe at 40%:
- The element shrinks to
80%scale while remaining level atrotate(0deg). This gives the eye time to register that the current canvas is moving back in 3D space. - Between
40%and100%, it accelerates into a-10degcounter-clockwise tilt and sinks down20%on the Y-axis.
The Diagonal Polygon Slice
The entrance uses CSS clip-path: polygon(...) to create an angular reveal:
At the from frame:
- The polygon
polygon(50% 50%, 100% 50%, 100% 100%, 50% 100%)isolates the bottom-right quadrant. - Combined with
translateX(100%) translateY(20%), the incoming page bursts out of that lower right quadrant and expands diagonally to coverpolygon(0% 0%, 100% 0%, 100% 100%, 0% 100%).
Because both pseudo-elements run hardware-accelerated transforms and clip-paths, this animation executes comfortably at 60-120fps without dropping frames.
Pattern 1: Standalone Component (document.startViewTransition)
If you are building an interactive showcase, catalog item, or single-page dashboard where views change within a container, you can trigger native View Transitions imperatively using document.startViewTransition:
[!IMPORTANT] Why
flushSyncis critical: React 18 and 19 batch state updates asynchronously.document.startViewTransition(callback)expects the DOM to reflect the new state immediately whencallback()resolves so it can snapshot the new view. WrappingsetActivePageinsideflushSyncguarantees that the DOM updates synchronously.
Pattern 2: Next.js App Router Multi-Page Template (<ViewTransition>)
In a real multi-page application with separate routes (/, /projects, /about, /info), you don't want a fake tab switcher. You want real URLs, SEO, browser back/forward history, and prefetching.
In Next.js 15+ and React 19, you can orchestrate this across genuine route changes using template.tsx and React's experimental <ViewTransition>:
1. The Template Wrapper (src/app/template.tsx)
Unlike layout.tsx (which persists across navigations), template.tsx remounts on every route change. This makes it the ideal place to assign the transition tags:
2. The Persistent Layout (src/app/layout.tsx)
Your navigation bar sits inside layout.tsx and maintains a permanent identity:
3. The Isolated Navbar (src/components/Navbar.tsx)
Assigning viewTransitionName: "navbar" tells the browser: "Do not snapshot or animate me as part of the page content. Keep me fixed in place."
Now, clicking any <Link href="..."> triggers the native browser View Transition automatically. Zero client routing hacks, zero heavy third-party routing wrappers.
The Mobile & Responsive Footgun: Viewport Scroll Locking
When adapting this transition to responsive viewports, a common bug occurs:
On desktop, your pages might fit neatly inside 100svh. But on mobile viewports:
- Two-column layouts collapse into a vertical stack (
flex-direction: column). - Images take vertical room (
55vh), pushing text down. - The total height exceeds
100svh.
If your sub-views don't have their own scroll container, users will find the page locked and unable to scroll down.
To fix this cleanly:
- Give each sub-view
height: 100svh; overflow-y: auto;. - Wrap scrollable columns in a smooth scroll container like
ReactLenis:
Now, on both mobile screens and narrow split-screen preview panels, visitors can smoothly scroll all the way to the bottom without breaking the transition.
Resources & Starter Template
You can explore and fork the ready-to-use template or try the component in Sora UI:
- Live Template Demo: tilt-slide-template.soralabs.studio
- GitHub Starter Repository: SoraLabsOSS/tilt-slide-template
- Sora UI Component: Tilt Slide Transition
Credits
Inspired by the editorial portfolio transition on Paul Kalkbrenner and the multi-page View Transition technique demonstrated by Codegrid. Reimplemented natively for React 19, TypeScript, and Next.js App Router.