fdb/docs

[§] /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

  • gsap with the Flip plugin.
  • Next.js 16 app router. React's ViewTransition works 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'); // reveal

Keep 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

Sources

On this page