[§] /docs/how-to/flip-layouts-and-page-transitions
How to animate layouts and pages with Flip
Animate any layout change, from a bento reshuffle to a route change, without measuring anything by hand.
TL;DR
- Flip: record positions, change the layout, then animate from the old positions.
- A page wipe is cover, swap, reveal.
- In Next.js, React's
<ViewTransition>morphs shared elements across routes.
Goal: layouts that move to their new shape instead of jumping, and pages that hand over smoothly.
You need
gsapwith theFlipplugin.- Next.js 16 app router. React's
ViewTransitionworks with no config.
First Last Invert Play ┌─┬─┐ ┌───┬─┐ measure old, animate to │A│B│ ► │ A │B│ ► put it back ► zero offset └─┴─┘ └───┴─┘ with transforms getState change classes Flip.from(state)
Steps
1. Record the state
import { Flip } from 'gsap/Flip';
gsap.registerPlugin(Flip);
const state = Flip.getState('.tile');2. Change the layout any way you like
Toggle a class, reorder the DOM, change grid spans. In React, set state and wait for the render to
commit (a useLayoutEffect or useGSAP keyed on the change).
grid.classList.toggle('expanded');3. Animate from the old state
Flip.from(state, { duration: 0.7, ease: 'power4.inOut', absolute: true, nested: true, stagger: 0.03 });Flip works out the difference and animates with transforms only, so it stays smooth. absolute: true
stops grid and flex from fighting it during the move.
4. Build a page wipe: cover, swap, reveal
const tl = gsap.timeline();
tl.to('.wipe', { clipPath: 'inset(0 0 0% 0)', duration: 0.45, ease: 'expo.in' }) // cover
.add(() => router.push(href)) // swap
.to('.wipe', { clipPath: 'inset(100% 0 0 0)', duration: 0.6, ease: 'expo.out' }, '+=0.15'); // revealKeep the cover short. People are waiting for the new page.
5. Or use View Transitions in Next.js
Wrap the element on both pages in <ViewTransition> with the same name. Route changes are already
transitions, so it morphs on its own.
import { ViewTransition } from 'react';
<ViewTransition name={`photo-${id}`}>
<Image src={src} alt={alt} width={800} height={500} />
</ViewTransition>Browsers without support simply skip the animation. Nothing breaks.
6. Respect reduced motion
For Flip, pass duration: 0 when reduced motion is on. For View Transitions, add
@media (prefers-reduced-motion: reduce) { ::view-transition-group(*) { animation: none; } }.
Why it works
When an element moves instead of vanishing, people see it is the same thing in a new place. They do not have to find it again.
See it live
[ex] in the wild · 13












