Moro
Gallery
IFoundations
01Why motion exists in UI
02Timing & duration
03Easing
04Springs vs tweens
IIExpression
05Anticipation
06Follow-through & overlapping action
07Squash, stretch & impact
08Arcs
IIIEngineering
09Performance
10Layout animations & FLIP
11Interruptibility & interaction
12Accessibility & restraint
Tier IIIEngineering10

Layout animations & FLIP

The trick behind every smooth rearrangement

Every time you see a list reorder itself smoothly, a card morph into a modal, or a tab indicator slide between tabs, the underlying mechanism is the same four-step dance called FLIP. It's one of the most important techniques in web animation, and it's often invisible because when it works, it just looks like magic. Understanding FLIP lets you implement shared-element transitions that would be impossible with naive CSS.

#The idea

FLIP stands for First, Last, Invert, Play. The technique was named by Paul Lewis in 2015 but has been used in graphics programming for decades. It lets you animate between two layout states, not just two positions, without knowing the destination ahead of time.

The steps:

  • First: measure the element's position before the layout change (getBoundingClientRect).
  • Last: perform the layout change and measure the element's new position.
  • Invert: apply a transform that makes the element look like it's still in its first position (a negative translate).
  • Play: animate the transform back to identity. The element appears to smoothly move from its First to its Last position, even though the layout change happened instantaneously.

FLIP is powerful because it animates layout results rather than specific positions. Drop an element into a grid, and the grid will reflow. FLIP makes that reflow look smooth. Sort a list, and each item slides to its new position.

Motion provides this via the layout prop and layoutId prop. You tag elements, and Motion handles the FLIP machinery automatically.

Canonical example

Open Moro's sidebar on the left. Click between lessons. Watch the dark rail slide between active items. That's layoutId doing FLIP. You didn't write keyframes for the slide; Motion measured before, measured after, and filled in the motion.

#When it applies

  • Lists reordering: dragging items, sorting, filtering.
  • Shared-element transitions: clicking a thumbnail that expands into a full view; the image appears to move from one place to another rather than fade and appear.
  • Tab indicators: the sliding underline between tabs.
  • Grid rearrangement: filtering a gallery; remaining items slide to fill gaps.
  • Modal-from-button: opening a dialog that visually emerges from the trigger button.

#When it doesn't

  • Simple enter/exit: fading a modal in has no FLIP component; you know both positions upfront.
  • Static layouts: if nothing rearranges, FLIP is overkill.
  • Text-heavy reflow: reflowing paragraphs of text with FLIP is expensive and often jarring; the text should just reflow instantly.

#Implementation

Auto-animating layout changes with layout:

AutoLayout.tsx
// Motion measures the element before/after any layout change
// and animates the difference for free.
<motion.div layout transition={{ duration: 0.3 }}>
  {children}
</motion.div>

Shared-element transition with layoutId:

SharedThumbnail.tsx
// Same layoutId across two components = FLIP between them
// when one unmounts and the other mounts.
 
{isOpen ? (
  <motion.div layoutId="card" className="fullscreen-modal">
    {content}
  </motion.div>
) : (
  <motion.div layoutId="card" className="thumbnail">
    {content}
  </motion.div>
)}
  • Both elements with the same layoutId share an identity. Motion measures the first, unmounts it, mounts the second, and animates from first to second.
  • This is exactly how the sidebar's rail works in Moro.

#Craft notes

  • layoutId is global. Two elements anywhere in the tree with the same layoutId will animate between each other. Namespace carefully.
  • layout animates all layout changes, including size, position, and aspect ratio. If you only want position, combine with specific properties.
  • Text distorts during layout animations unless you wrap it. The workaround is to animate the container layout and keep the text size stable via layout='position'.
  • FLIP can be expensive on large element counts. Reordering 100 items with layout will measure each. Use AnimatePresence with manual exit animations for very large lists.
  • Nanda's essay is the canonical teaching of FLIP. If this lesson is the 101, Nanda's is the 200-level. Worth reading after this one.

#Exercises

  1. Build a tab indicator. Create three tabs. Add a sliding underline using layoutId. Without FLIP, you'd need to compute positions manually. With layoutId, Motion does it.
  2. Animate a filter. Take a grid of items with a filter. Apply layout to each item. As the grid filters down, remaining items should slide to their new positions.
  3. Shared element between routes. Try to build a thumbnail-to-detail transition where the thumbnail image morphs into the hero of the detail view. This is the hardest version of FLIP to do well.

#Study this in the wild

  • Moro's own sidebar. Sliding rail on active lesson uses layoutId. Open DevTools and watch the transforms during click.
  • Linear's issue panel. Opening an issue from the list uses shared-element transitions. The issue title's position appears stable as the panel expands around it.
  • iOS photo app. Tapping a thumbnail expands it to full screen via shared-element. Native implementation, same principle.

#Further reading

  • Nanda: Inside Framer Motion's Layout Animations — the definitive pedagogical essay on FLIP.
  • Paul Lewis: FLIP your animations — the 2015 original that named the technique.
  • Motion: Layout animations.
Lesson 09
Performance
Lesson 11
Interruptibility & interaction

On this page

The ideaWhen it appliesWhen it doesn'tImplementationCraft notesExercisesStudy this in the wildFurther reading

Built by David Umoru