Most animation libraries animate into a state. You define a start point and an end point. GSAP Flip does the opposite. It captures where elements are, mutates the DOM, then animates elements back to their old positions. To the user, it looks like elements are sliding into their new homes. To the code, it's a backwards animation on a forwards state change. This inversion is why Flip feels magical. Users experience pure, purposeful motion tied to a real DOM change. No tweening numbers. No fake timelines. Just the browser recording, changing, and replaying.
Aidxn uses Flip in Velocity X for case study transitions: click a project, the hero image animates from grid position to full-width detail view. Click back, it animates back. No page load. No route change. Just a layout mutation that feels like cinema. Or category filters on portfolio grids: uncheck "motion design", a dozen items vanish, the remaining grid reflows, and every item animates down into its new spot. The user's eyes follow the motion. The layout change reads as intentional, not jarring.
How Flip Works: The Three-Step Ritual
Flip has a simple contract: capture state, change state, replay the difference.
Step 1: Capture. Call Flip.getState(selector). Flip records the position, size, and rotation of every matched element. It's a snapshot: x, y, width, height. Nothing about color or opacity. Just geometry in the DOM tree.
Step 2: Mutate. Change the DOM however you want. Remove items. Add items. Reorder. Change classes. Update data attributes. Flip doesn't care. It just watches.
Step 3: Animate. Call Flip.from(state, config). Flip compares the old state (before mutation) to the current state (after mutation). For each element, it calculates the delta: how far did this element move? How much did it resize? It then positions each element back to its old spot and animates from that position to the new one. Hence "from" — the animation runs from old to new, not new to old.
The result: every element in the mutated region animates smoothly into place. A grid reflow looks like items are repositioning themselves. A deleted item slides away. A reordered list dances into new order. All without manually defining animation vectors or tweening coordinates yourself.
Three Patterns That Define Smart UX
Pattern 1: Filter Grid Reveal
User clicks "hide motion design". Items filtered out vanish instantly (DOM removed). Remaining items animate down into a tighter grid. No reflow jank. Pure motion.
// HTML
<button class="filter-btn" data-tag="motion">Hide Motion</button>
<div class="grid" id="grid">
<div class="item" data-tag="motion">Motion Project</div>
<div class="item" data-tag="web">Web Project</div>
<div class="item" data-tag="motion">Another Motion</div>
</div>
// JavaScript
import gsap from 'gsap';
import Flip from 'gsap/Flip';
gsap.registerPlugin(Flip);
document.querySelector('.filter-btn').addEventListener('click', (e) => {
const tag = e.target.dataset.tag;
const grid = document.querySelector('#grid');
// Step 1: Capture the current state
const state = Flip.getState('.item');
// Step 2: Mutate the DOM
document.querySelectorAll(`.item[data-tag="${tag}"]`).forEach(el => {
el.remove();
});
// Step 3: Animate the difference
Flip.from(state, {
duration: 0.6,
ease: 'power2.inOut',
stagger: 0.05
});
});
That's it. Three steps, 20 lines of code. The browser handles the rest. Items that weren't removed stay in the DOM but move to new grid positions. Flip animates that movement. Deleted items? Flip records them but since they're gone, they don't animate — they're already off the page. The visual effect: filtered items slide down smoothly, the grid tightens, and the user feels like the site is responding intelligently to their input.
Pattern 2: Hero to Detail Transition
Click a portfolio item in a grid thumbnail. The image animates from grid-thumbnail size/position to full-width hero on a detail page. Click back. It animates from hero back to thumbnail.
// Grid view
<div class="portfolio-grid">
<div class="grid-item" data-id="project-1">
<img src="..." alt="Project" />
</div>
</div>
// Detail view (pre-rendered off-screen or fetched)
<div class="detail-view" style="display: none;">
<img id="hero-img" src="..." alt="Project" />
</div>
// Click handler
document.addEventListener('click', async (e) => {
const gridItem = e.target.closest('.grid-item');
if (!gridItem) return;
// Capture grid state
const gridState = Flip.getState(gridItem);
// Fetch or reveal detail view
const detailView = document.querySelector('.detail-view');
const heroImg = detailView.querySelector('img');
detailView.style.display = 'block';
// Move the image to the detail view
// (In real code, you'd handle srcset and responsive images)
heroImg.src = gridItem.querySelector('img').src;
// Animate from grid position to detail position
Flip.from(gridState, {
duration: 0.8,
ease: 'expo.inOut'
});
});
In production, this would handle responsive srcsets, loading states, and route transitions (especially with Astro ViewTransitions). The principle stays the same: capture the thumbnail, show the detail, animate the image from small to large. The motion grounds the interaction. Users understand that the detail view is the expanded version of what they clicked. This is how luxury watch brands and high-end portfolios feel.
Pattern 3: Reorder Animation
Sortable list. User drags "Item C" to the top. Items reorder instantly in the DOM. Then Flip animates the visual reflow: "Item A" and "Item B" slide down to make room. No jank. Pure choreography.
// HTML (simplified)
<ul id="sortable-list">
<li data-id="1">Item A</li>
<li data-id="2">Item B</li>
<li data-id="3">Item C</li>
</ul>
// JavaScript (with a sortable library, e.g., Sortable.js)
import gsap from 'gsap';
import Flip from 'gsap/Flip';
import Sortable from 'sortablejs';
gsap.registerPlugin(Flip);
const list = document.getElementById('sortable-list');
Sortable.create(list, {
onEnd: (evt) => {
// Capture state BEFORE reordering
const state = Flip.getState('li');
// Reorder the DOM (Sortable already did this)
// But if using a custom handler, you'd move the DOM node here
// Animate from old positions to new
Flip.from(state, {
duration: 0.5,
ease: 'back.out',
stagger: 0.03
});
}
});
The reorder happens in the DOM. Flip records the before state. The items are now in new positions. Flip animates them sliding from old to new. The user sees a smooth rearrangement. No re-rendering, no React key warnings, no fighting the DOM. Just physics and motion.
Real Code: Velocity X Case Study Transitions
This is the actual pattern used in Velocity 9 portfolio templates for case study reveals:
// CaseStudyGrid.tsx
import gsap from 'gsap';
import Flip from 'gsap/Flip';
import { useState } from 'react';
gsap.registerPlugin(Flip);
export const CaseStudyGrid = ({ cases }) => {
const [selectedId, setSelectedId] = useState(null);
const handleCaseClick = async (caseItem) => {
const thumbnail = document.querySelector(
`[data-case-id="${caseItem.id}"]`
);
// Step 1: Capture thumbnail position
const thumbnailState = Flip.getState(thumbnail);
// Step 2: Show the detail view (with ViewTransitions or CSS)
setSelectedId(caseItem.id);
// Step 3: After detail renders, animate the hero image
// Use a transition callback to ensure DOM is updated
requestAnimationFrame(() => {
const heroImg = document.querySelector('.case-detail-hero');
if (!heroImg) return;
Flip.from(thumbnailState, {
targets: heroImg,
duration: 0.75,
ease: 'power3.inOut',
onComplete: () => {
// Optional: hide the original thumbnail
thumbnail.style.opacity = 0;
}
});
});
};
return (
<>
<div className="grid grid-cols-3 gap-6">
{cases.map((c) => (
<div
key={c.id}
data-case-id={c.id}
onClick={() => handleCaseClick(c)}
className="cursor-pointer"
>
<img src={c.thumb} alt={c.title} />
</div>
))}
</div>
{selectedId && <CaseDetail id={selectedId} />}
</>
);
};
Click a case thumbnail. Its position is recorded. The detail view appears (via state or route change). The hero image in the detail view animates from thumbnail coordinates to full-width hero coordinates. Click the back button, reverse the animation, the detail disappears, and the thumbnail is back where it started. This is what Velocity X users see when they click "View Case Study" on the homepage. The motion is why they remember the interaction. That's the ROI of Flip.
Integration with Astro and ViewTransitions
Astro's ViewTransitions let you animate between pages. Combine ViewTransitions with Flip and you get page-to-page animations that feel native.
// Before ViewTransition fires
// Capture state of elements you want to animate
const state = Flip.getState('.animated-element');
// Let ViewTransition run (page swap)
// In the new page's onLoad hook:
Flip.from(state, {
duration: 0.6,
ease: 'power2.inOut'
});
This pairs beautifully with route-based detail views. Navigate from /portfolio to /portfolio/case-study, the thumbnail animates to hero. Navigate back, the hero animates to thumbnail. It feels like a single app, not page loads.
Six FAQs
Does Flip work with React/Vue/Svelte?
Yes. Flip captures the DOM, not the framework state. Trigger Flip after the DOM has been mutated (after setSelectedId, after component re-render). Use refs or requestAnimationFrame to ensure the DOM is updated before calling Flip.from().
What if elements are deleted? Do they animate?
No. Deleted elements aren't in the new DOM, so Flip can't animate them. They vanish instantly. Only elements that exist in both old and new states animate. This is often the desired behavior — fading out deleted items requires a separate gsap.to() call.
Can I use Flip with CSS Grid or Flexbox?
Yes. Flip records final positions, regardless of layout method. Change grid-template-columns, items reflow, Flip animates them into place. Works with any layout engine.
Is Flip compatible with lazy-loaded images?
Yes, but ensure images are loaded before capturing state. If an image is loading when you call Flip.getState(), its position might be wrong (tall placeholder vs. actual image height). Wait for onload or use a custom IntersectionObserver to detect when the image is ready.
What's the performance cost?
Minimal. Flip is just geometry math: recording coordinates, calculating deltas, applying transforms. It's not re-rendering or re-painting the entire page. On a 100-item grid, Flip adds maybe 2–5ms of overhead. Use stagger carefully (0.03–0.05s between items) to avoid too many simultaneous animations on older devices.
Can I chain Flip animations?
Yes. Use Flip.from() inside a timeline. Capture state A, mutate, animate, mutate again, animate again. Each call to Flip.from() reads the current DOM and animates the difference. Chain them with then() callbacks or inside a gsap.timeline().
The Bottom Line
Flip is the secret weapon of premium websites. It turns DOM mutations into choreographed sequences. Users don't see code executing. They see elements dancing into place. Grids tighten. Images zoom. Lists rearrange. All with the fluidity of real motion.
The browser's native layout engine reflows instantly — it doesn't animate. CSS transitions require you to define start and end states. Flip inverts the problem: change the state, then animate from old to new. It's a perspective shift that unlocks possibilities. Any DOM mutation becomes a potential animation. Any state change becomes a story.
For more scroll-driven motion, check out GSAP ScrollTrigger's pin and pinspacer properties for the Apple-pattern sticky reveals. Both are tools in the same toolbox. Motion is the medium. Velocity X templates ship with both wired up.