CSS Animations in React — the FLIP technique

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Builds on frontend/foundations: box model — FLIP measures box positions; the box model is the foundation.

state-driven transitions → FLIP: animating position changes

React mutates the DOM; the browser paints. There is no animation layer in React itself — you opt into smoothness with CSS. The simple case: toggle a class/style on a state change and transition: background .3s fills the in-between frames. But when an element changes position (a list reorders, a card grows), CSS transitions are blind to it — React re-renders and the box just snaps to its new slot. FLIP fixes that by turning a position change into a transform, which the GPU animates cheaply.

FLIP stepwhat you docode
First record the element's old box before the change old = el.getBoundingClientRect()
Last let React move the DOM to its new position (state update + re-render) setItems(reordered)
Invert compute dy = old.top − new.top; apply a transform so it looks unmoved el.style.transform = 'translateY(' + dy + 'px)'
Play drop the transform with a transition — the box glides to its real spot transition: transform .3s; transform = ''

The measuring happens in useLayoutEffect (runs after DOM mutation, before paint) so the user never sees the snap — only the smooth glide. This is the engine inside Framer Motion's layout prop and the View Transitions API.

1 · the animations you write (edit me)

2 · Babel compiles JSX → element tree

<App/> becomes React.createElement(App). Each setItems() re-renders; useLayoutEffect then measures every keyed <li> and runs First → Last → Invert → Play before paint.

// (hit "compile & render" to see Babel's output)

3 · live React (FLIP, proven)

Click the card to toggle a smooth color transition. Click ↑/↓ to reorder the list — each item glides to its new slot instead of snapping. The gold-check clicks ↑ on Beta automatically and asserts the DOM order becomes [Beta, Alpha, Gamma].

order: · gold: 3 items, Alpha first → move-up Beta → [Beta, Alpha, Gamma]

intent → animation pattern

intentpatternwhy
color / size / opacity change style={{transition:'.3s'}} + toggle className on state state-driven — no measuring needed; browser fills frames
element moves / reorders FLIP in useLayoutEffect (measure → invert → play) React re-render snaps; FLIP turns the move into an animated transform
mount / unmount animation defer removal: keep node, add exit class, remove on transitionend React unmounts instantly — you must delay it to animate out
shared-element / crossfade native document.startViewTransition() (see view_transitions) browser does FLIP for you across the whole page
physics / gestures / drag Framer Motion motion + layout prop library wraps FLIP + spring physics + AnimatePresence exits