# Icon transitions Cross-fade recipes for an icon that swaps on a state change, with and without a motion library. Icon weight, color and direction live in [icons.md](icons.md). ## Which library Check the project's `package.json`. Import from `"motion/react"` when `motion` is installed, or `"framer-motion"` when that is. Where both exist, follow the imports the component or its nearest peers already use. Where neither is present, use the CSS cross-fade and never add a dependency just for icon transitions. ## Motion ```tsx import { AnimatePresence, motion } from "motion/react"; function IconButton({ isActive, icon: Icon }) { return ( ); } ``` `initial={false}` keeps the default icon from animating in on mount. ## CSS, both icons in the DOM Both icons stay mounted in one grid cell, so enter and exit both animate and the larger icon sets the size. The `motion-reduce:` classes leave only the opacity fade under reduced motion. ```tsx const layer = cn( "[grid-area:1/1] transition-[opacity,filter,scale] duration-300", "ease-[cubic-bezier(0.2,0,0,1)]", "motion-reduce:scale-100 motion-reduce:blur-[0px]" ); const shown = "scale-100 opacity-100 blur-[0px]"; const hidden = "scale-[0.25] opacity-0 blur-[4px]"; function IconButton({ isActive, ActiveIcon, InactiveIcon }) { return ( ); } ``` Tailwind 4's `scale-*` utilities write the `scale` property, so `transition-[...scale]` animates them. Tailwind 3's write `transform`, so name `transform` in the transition instead. ## Which icons animate | Animate | Don't animate | | --- | --- | | Infrequent state swaps such as play to pause, like to liked or copy to copied | Hover-revealed actions in list rows | | Icons in a toolbar that appears on selection | Tab and navigation icon swaps | | Loading and success indicators | Decorative or always-visible icons |