Shared Layout
One element moving beats two elements swapping.
When a tab indicator jumps from one tab to the next, your brain sees two things: a highlight disappearing and a new one appearing. When it slides, you see one thing moving - and movement carries information: where it came from, where it went, that these two states are the same object.
One element, one `layoutId`. With it, the indicator travels between tabs and the switch reads as movement; without it, the highlight teleports and the tabs feel unrelated.
How it works
Give the element a layoutId and Motion does the rest:
{active === i && (
<motion.span layoutId="indicator" className="..." />
)}
Under the hood this is FLIP: measure the element's old position, render it
in the new one, then animate a transform between the two. Because it's all
transform, it runs on the compositor - a layoutId slide is cheaper than
animating left or width ever was.
Where it earns its keep
- Tab and nav indicators - the canonical case.
- Preview to detail - a card that expands into a dialog reads as the same surface growing, not a modal appearing over it.
- Reordering lists - items glide to their new slots instead of teleporting.
Notes
- Keep shared elements outside
AnimatePresencewhen you can - exit animations and layout measurement fight each other. - Rounded corners distort under scale; Motion corrects
borderRadiusif it's set via thestyleprop. - Reach for it when two states are conceptually one object. If they aren't, a crossfade is honest and a morph is noise.