Page Transition
A page transition animates the move between routes instead of a hard cut, a cover that wipes across, a shared element that morphs from one page to the next, or a fade that masks the swap. It keeps context and makes a multi-page site feel like one continuous surface. Built with a leave/enter pair coordinated through GSAP, often using the Flip plugin for shared-element morphs.
Updated June 2, 2026
Components that solve this
Tuning the pattern
- Transition type
- A fast fade is the cheapest and right for app surfaces where latency matters; a cover wipe (built from the Mask Reveal mechanic) suits content sites that want a sense of flow; a shared-element morph (Flip Zone's Flip technique) is the highest payoff and the highest cost, so reserve it for the one element, a logo or a card, that genuinely carries between two pages.
- Leave/enter pairing
- The leaving page plays the cover-in and the entering page plays the cover-out, and both halves must pick the same mechanism from both endpoints; let leave and enter decide independently and a marketing-to-app navigation will mismatch the raised cover against the opened one, so store the chosen mechanism once and have both halves read it.
- Scroll handling
- Reset to top so the new page starts clean, but know that an element already in the first viewport after the reset will not fire ScrollTrigger's onEnter (it is already past the start); decide whether each reveal is in the first viewport once, at reveal time, and play those in-view elements directly instead of leaving them statically visible.
When a page transition fits
Page transitions make a site feel cohesive and intentional. They suit experiences where the journey between pages is part of the story.
- Portfolio and agency sites where navigation should feel seamless
- Editorial and campaign sites with a strong sense of flow
- Product sites where a shared element (a logo, a card) should carry between pages
- Any multi-page site that wants to feel like a single continuous surface
Transitions add perceived latency
How the transition works
A transition is a leave/enter pair. The leaving page plays a cover-in (or fade-out), the route swaps, and the entering page plays a cover-out (or fade-in). The critical rule: both halves must pick the SAME mechanism from BOTH endpoints, or the raised cover and the opened cover mismatch when navigating across different layout contexts.
For shared-element transitions, GSAP's Flip plugin records an element's position on the leaving page and animates it from there to its new position on the entering page, even across DOM re-renders.
Annnimate components that solve this pattern
Annnimate doesn't ship a drop-in 'page transition' component because transitions are tightly coupled to your router and layout. The building blocks are here:
- The Flip Zone component demonstrates the GSAP Flip technique that powers shared-element transitions, recording and morphing element state across a layout change
- The Mask Reveal component provides the cover-wipe mechanic a cover-style transition is built from
Common questions
- What's the GSAP Flip plugin and why does it matter for transitions?
- Flip records an element's current size and position, lets you change the DOM however you want, then animates the element from its old state to its new one. For shared-element page transitions, that means a card on page A can smoothly morph into its larger version on page B, even though they're different DOM nodes. Flip is free for commercial use since GSAP 3.13.
- Why does my transition break when navigating between different page types?
- Because the leave and enter halves decided their mechanism independently. When you go from a marketing layout to an app layout, the raised cover and the opened cover can mismatch. The fix is to pick the transition mechanism once, from both endpoints, and store it so both halves agree.
- Should I reset scroll position during a transition?
- Usually yes - reset to top so the new page starts at the top. But watch out: if the new page's entrance animations use ScrollTrigger's onEnter, an element already in the first viewport after the reset won't fire onEnter (it's already past the start). Decide 'is it in the first viewport' once, at reveal time, and play those in-view elements directly.
