Icon Morph

Swapping icons with blur, scale, and a spring.

Icons that change state — copy to check, play to pause, menu to close — feel abrupt when they swap in a single frame. But a full crossfade is too slow and mushy. The sweet spot is a short blur + scale + fade, driven by a spring.

The blur is what sells it: as the old icon shrinks it melts out of focus, and the new one condenses into sharpness. It reads as transformation, not replacement. Toggle the morph off below to feel the difference.

blur
scale
morph

The recipe

<AnimatePresence mode="popLayout" initial={false}>
  <motion.span
    key={copied ? "check" : "copy"}
    initial={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
    animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
    exit={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
    transition={{ type: "spring", duration: 0.3, bounce: 0 }}
  >
    {copied ? <IconCheck /> : <IconCopy />}
  </motion.span>
</AnimatePresence>

Details that matter:

  • mode="popLayout" takes the exiting icon out of layout so the two icons overlap instead of stacking. Wrap in a relative container.
  • initial={false} prevents the animation from firing on first mount.
  • bounce: 0 — a critically damped spring. Icon swaps want snap, not wobble.
  • Keep it around 300ms; state feedback should never make the user wait.

The copy-link button in this site's header is exactly this component.

Resources

Search concepts

Search for a command to run...