Production-quality animations — from micro-interactions to complex choreography
npm install framer-motionFramer Motion is a production-ready animation library for React. It provides a declarative API for animations, gestures, and layout transitions that would take hundreds of lines of CSS or vanilla JavaScript to achieve.
Framer Motion works by replacing standard HTML elements with motion equivalents. A <div> becomes <motion.div>, a <button> becomes <motion.button>, and so on.
import { motion } from "framer-motion"
// Instead of:
<div className="card">...</div>
// Use:
<motion.div className="card">...</motion.div>Every motion component accepts animation props like initial, animate, exit, transition, whileHover, and whileTap.
The simplest animation: fade an element in when it appears on the page.
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<h1>Welcome</h1>
</motion.div>Wrap this pattern in a reusable component that respects the user's motion preferences:
"use client"
import { motion, useReducedMotion } from "framer-motion"
import { ReactNode } from "react"
interface FadeInProps {
children: ReactNode
delay?: number
duration?: number
className?: string
}
export function FadeIn({
children,
delay = 0,
duration = 0.5,
className,
}: FadeInProps) {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: shouldReduceMotion ? 0 : duration,
delay: shouldReduceMotion ? 0 : delay,
ease: "easeOut",
}}
className={className}
>
{children}
</motion.div>
)
}
// Usage:
// <FadeIn>
// <Card />
// </FadeIn>
//
// <FadeIn delay={0.2}>
// <Card />
// </FadeIn>Micro-interactions give your UI a polished, responsive feel. Framer Motion makes hover and tap effects trivial.
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
className="bg-action-primary text-white px-6 py-3 rounded-button"
>
Click Me
</motion.button><motion.div
whileHover={{
y: -8,
boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1)",
}}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
className="bg-white rounded-card p-6 border"
>
<h3>Hover to lift</h3>
<p>This card rises on hover with a spring animation.</p>
</motion.div><motion.div
whileHover={{ rotate: 90 }}
transition={{ type: "spring", stiffness: 200, damping: 10 }}
>
<SettingsIcon className="w-6 h-6" />
</motion.div>When rendering a list of items, stagger their entrance so they appear one after another instead of all at once.
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
}
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.4,
ease: "easeOut",
},
},
}function CardGrid({ cards }: { cards: CardData[] }) {
return (
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
className="grid grid-cols-1 md:grid-cols-3 gap-6"
>
{cards.map((card) => (
<motion.div
key={card.id}
variants={itemVariants}
className="bg-white rounded-card p-6 border"
>
<h3 className="font-semibold">{card.title}</h3>
<p className="text-text-secondary mt-2">{card.description}</p>
</motion.div>
))}
</motion.div>
)
}By default, React removes components from the DOM immediately. AnimatePresence lets you animate components out before they are removed.
import { motion, AnimatePresence } from "framer-motion"
import { useState } from "react"
function ToastDemo() {
const [toasts, setToasts] = useState<string[]>([])
const addToast = () => {
const id = Date.now().toString()
setToasts((prev) => [...prev, id])
setTimeout(() => {
setToasts((prev) => prev.filter((t) => t !== id))
}, 3000)
}
return (
<div>
<button onClick={addToast}>Show Toast</button>
<div className="fixed bottom-4 right-4 space-y-2">
<AnimatePresence>
{toasts.map((id) => (
<motion.div
key={id}
initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 100 }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
className="bg-gray-900 text-white px-4 py-3 rounded-lg shadow-lg"
>
This is a toast notification
</motion.div>
))}
</AnimatePresence>
</div>
</div>
)
}| Duration | Category | Use For |
|---|---|---|
| 0–100ms | Micro | Button presses, toggles, color changes |
| 100–300ms | Short | Tooltips, dropdowns, fade-ins |
| 300–500ms | Medium | Page transitions, modals, expanding panels |
| 500ms+ | Too Slow | Avoid. Users perceive delays over 500ms as laggy. Only use for decorative animations that don't block interaction. |
| Easing | When to Use | Framer Motion Syntax |
|---|---|---|
easeOut | Elements entering the screen (fast start, gentle stop) | { ease: "easeOut" } |
easeIn | Elements leaving the screen (gentle start, fast exit) | { ease: "easeIn" } |
easeInOut | Elements moving on screen (smooth both ends) | { ease: "easeInOut" } |
spring | Interactive elements (buttons, cards, drags) | { type: "spring", stiffness: 300, damping: 20 } |
linear | Progress bars, loading spinners (constant speed) | { ease: "linear" } |
Some users experience motion sickness or discomfort from animations. The prefers-reduced-motion media query lets you respect their system setting.
import { motion, useReducedMotion } from "framer-motion"
function AnimatedCard({ children }: { children: React.ReactNode }) {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: shouldReduceMotion ? 0 : 0.4,
}}
whileHover={shouldReduceMotion ? {} : { y: -4 }}
>
{children}
</motion.div>
)
}For CSS-based animations, use the media query directly:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}Always test your animations with reduced motion enabled. On macOS, go to System Settings → Accessibility → Display → Reduce motion.