FLIP, Layout & View Transitions
Choreographing elements as the DOM changes.
The hardest motion problem isn't moving one box — it's choreographing many as the DOM rearranges itself: a list re-sorts, a thumbnail blooms into a detail page, a route changes. You can't cheaply animate layout (Chapter 0's tier list says so), so the whole game is: measure, then fake it with transforms.
FLIP: First, Last, Invert, Play
FLIP is the foundational trick, coined by Paul Lewis. Layout properties (top, width, grid order) are expensive and not compositable — but transform is. So instead of animating layout, you let the layout change instantly, then lie about it with a transform that the compositor animates away.
The View Transitions API: FLIP, built into the browser
FLIP is fiddly: you track nodes, snapshot rects, juggle entering/leaving elements. The View Transitions API bakes the pattern into the platform. You hand it a
callback that mutates the DOM; the browser snapshots the old state as an image, runs your
update, snapshots the new state, and cross-fades — or morphs — between them via a
generated pseudo-element tree (::view-transition-group, ::view-transition-old/new).
const vt = document.startViewTransition(() => updateTheDOM());
// In SvelteKit the mutation is reactive, so AWAIT the DOM commit:
const vt = document.startViewTransition(async () => {
stateChange(); // flip a $state value
await tick(); // wait for Svelte to patch the DOM
}); Give two elements — one in the old snapshot, one in the new — the same view-transition-name and the browser pairs them: it interpolates position,
size and opacity, producing a true morph instead of a cross-fade. Names must be unique within a
single snapshot.
Baseline Newly Available on Oct 14 2025 — Firefox 144 was the last engine to ship. Chrome/Edge 111+, Safari 18+.
Chromium-only (Chrome/Edge 126+). For multi-page navigations, feature-detect and degrade to an instant swap.
Demo: re-sort a grid two ways
The same nine cards, the same re-ordering — animated by hand-rolled FLIP or by
the View Transitions API. Flip the toggle and watch both visibly morph cards
to their new slots. The simulate unsupported browser switch forces the FLIP fallback
path even where startViewTransition exists — exactly the progressive-enhancement
branch you'd ship.
Hand-rolled FLIP: measure → mutate → invert → element.animate() to zero.
Shared-element transition: thumbnail → detail
The signature move: a small card that blooms into a full panel, the same surface
morphing in place rather than two unrelated elements fading. With the API it's one view-transition-name shared between the thumbnail and the panel. Without it, FLIP
does the same job — measure the thumb rect, measure the panel rect, invert with translate +
scale, play.
Click a card to expand it. Using the FLIP translate+scale fallback.
The framework-native shortcuts
You rarely hand-roll FLIP in 2026 — every framework wraps it:
- Svelte ships
animate:flip(the literal FLIP technique, applied to keyed{#each}reordering) and thecrossfadetransition for shared-element moves between two lists — both built into the runtime. - Motion (and Framer Motion) expose the
layoutprop: addlayoutto a component and any layout change is FLIP-animated automatically;layoutIdpairs elements across trees for shared-element morphs — the same idea asview-transition-name, done in JS. - AutoAnimate is a zero-config drop-in that FLIP-animates add/remove/move on any container's direct children.
Playground: name it, toggle a class, watch it morph
Native View Transitions, no library. Click morph layout to toggle a class that
rearranges the boxes; startViewTransition interpolates the change. Each box has a view-transition-name, so they morph rather than cross-fade — delete those lines to
feel the difference. The starter code feature-detects and degrades gracefully.