The Animation Challenge
Modern web applications need smooth transitions to feel polished and professional. When components appear and disappear from the DOM, abrupt changes can feel jarring. Users expect subtle animations that guide their attention and provide visual feedback.
The problem? React's conditional rendering happens instantly:
{isVisible && <div>Content appears instantly!</div>}
This works functionally, but there's no transition. The content just pops into existence. We need a way to animate the entrance and exit of conditionally rendered components.
Solution
Framer Motion's AnimatePresence component solves this by tracking components as they're added and removed from the React tree, giving us time to animate their exit before unmounting.
Here's a reusable wrapper component:
import { motion, AnimatePresence } from 'framer-motion'
interface AnimatePresenceProps {
isVisible: boolean
children: React.ReactNode | React.ReactNode[]
animationProps?: React.ComponentProps<typeof motion.div>
}
const AnimateAppearance = ({
isVisible,
children,
animationProps,
}: AnimatePresenceProps) => (
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: -10, opacity: 0 }}
transition={{ delay: 0.3, duration: 0.3 }}
{...(animationProps as any)}
>
{children}
</motion.div>
)}
</AnimatePresence>
)
export default AnimateAppearance
How It Works
AnimatePresence: Wraps conditionally rendered components and delays their unmounting until exit animations complete.
Motion states:
initial- Starting position (slightly below, invisible)animate- Final position (at rest, fully visible)exit- Leaving position (slightly above, invisible)
Transition: Controls timing with 300ms duration and delay, creating a smooth fade-and-slide effect.
Prop spreading: The animationProps parameter lets you override default animations per instance, giving flexibility without sacrificing reusability.
Practical Usage
import AnimateAppearance from '@/components/AnimateAppearance'
const NotificationBanner = () => {
const [showBanner, setShowBanner] = useState(true)
return (
<AnimateAppearance isVisible={showBanner}>
<div className="banner">
<p>Your changes have been saved!</p>
<button onClick={()=> setShowBanner(false)}>Dismiss</button>
</div>
</AnimateAppearance>
)
}
For custom animations per use case:
<AnimateAppearance
isVisible={showModal}
animationProps={{
initial: { scale: 0.9, opacity: 0 },
animate: { scale: 1, opacity: 1 },
exit: { scale: 0.9, opacity: 0 },
transition: { duration: 0.2 },
}}
>
<Modal />
</AnimateAppearance>
Why This Matters
Better UX: Smooth animations feel more professional and guide user attention naturally.
Reusability: Write the animation logic once, use it anywhere.
Flexibility: Override defaults when needed while keeping sensible defaults for common cases.
Type safety: Using React.ComponentProps<typeof motion.div> (see TS/React #01: Where are my prop types?!) ensures full autocomplete and type checking for animation properties.
Common Variations
Slide from different directions:
// From right
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
exit={{ x: -20, opacity: 0 }}
Scale animation:
initial={{ scale: 0.95, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.95, opacity: 0 }}
Stagger children:
<motion.div
initial="hidden"
animate="visible"
variants={{
visible: { transition: { staggerChildren: 0.1 } }
}}
>
{items.map(item => <AnimateAppearance>{item}</AnimateAppearance>)}
</motion.div>
Performance Considerations
- Framer Motion uses hardware-accelerated transforms (
opacity,x,y,scale) for smooth 60fps animations - Avoid animating properties like
height,width, ormarginas they trigger layout recalculations (but there are uses to it!) - Use
mode="wait"onAnimatePresenceto prevent multiple animations overlapping
Conclusion
The AnimateAppearance component provides a clean abstraction over Framer Motion's animation primitives. By encapsulating the common pattern of appearance/disappearance animations, we can:
- Add polish to conditional rendering with minimal code
- Maintain consistency across the application
- Customize animations when needed without reinventing the wheel
- Keep components clean and focused on their primary purpose
Next time you're rendering something conditionally, remember: a little motion goes a long way in creating delightful user experiences.