Chapter 6

FLIP, Layout & View Transitions

Choreographing elements as the DOM changes.

animate:flipView Transitions APIAutoAnimate
FLIP measures first/last and inverts; the View Transitions API snapshots → updates → animates. Same-document is Baseline (Oct 2025); cross-document is Chromium-only.

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.

F
First
measure the start rect with getBoundingClientRect()
L
Last
mutate the DOM, then measure the end rect
I
Invert
apply a transform so it LOOKS unmoved
P
Play
animate that transform to zero — on the compositor

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.

View Transitions (same-document / SPA) Baseline 2025
Chrome/Edge
Firefox
Safari

Baseline Newly Available on Oct 14 2025 — Firefox 144 was the last engine to ship. Chrome/Edge 111+, Safari 18+.

View Transitions (cross-document / MPA)
Chrome/Edge
Firefox
Safari

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.

Sortable / filterable grid — FLIP vs View Transitions live demo
translate core
opacity core
scale core
blur fx
glow fx
shadow fx
pointer io
scroll io
keydown io
mode: hand-rolled FLIP last animation: —
technique

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.

Shared-element expand (morph or FLIP fallback) live demo

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 the crossfade transition for shared-element moves between two lists — both built into the runtime.
  • Motion (and Framer Motion) expose the layout prop: add layout to a component and any layout change is FLIP-animated automatically; layoutId pairs elements across trees for shared-element morphs — the same idea as view-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.