Interaction Design
Create engaging, intuitive interactions through motion, feedback, and thoughtful state transitions that enhance usability and delight users.
When to Use This Skill
-
Adding microinteractions to enhance user feedback
-
Implementing smooth page and component transitions
-
Designing loading states and skeleton screens
-
Creating gesture-based interactions
-
Building notification and toast systems
-
Implementing drag-and-drop interfaces
-
Adding scroll-triggered animations
-
Designing hover and focus states
Core Principles
- Purposeful Motion
Motion should communicate, not decorate:
-
Feedback: Confirm user actions occurred
-
Orientation: Show where elements come from/go to
-
Focus: Direct attention to important changes
-
Continuity: Maintain context during transitions
- Timing Guidelines
Duration Use Case
100-150ms Micro-feedback (hovers, clicks)
200-300ms Small transitions (toggles, dropdowns)
300-500ms Medium transitions (modals, page changes)
500ms+ Complex choreographed animations
- Easing Functions
/* Common easings / --ease-out: cubic-bezier(0.16, 1, 0.3, 1); / Decelerate - entering / --ease-in: cubic-bezier(0.55, 0, 1, 0.45); / Accelerate - exiting / --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); / Both - moving between / --spring: cubic-bezier(0.34, 1.56, 0.64, 1); / Overshoot - playful */
Quick Start: Button Microinteraction
import { motion } from "framer-motion";
export function InteractiveButton({ children, onClick }) { return ( <motion.button onClick={onClick} whileHover={{ scale: 1.02 }} whileTap={{ scale: 0.98 }} transition={{ type: "spring", stiffness: 400, damping: 17 }} className="px-4 py-2 bg-blue-600 text-white rounded-lg" > {children} </motion.button> ); }
Interaction Patterns
- Loading States
Skeleton Screens: Preserve layout while loading
function CardSkeleton() { return ( <div className="animate-pulse"> <div className="h-48 bg-gray-200 rounded-lg" /> <div className="mt-4 h-4 bg-gray-200 rounded w-3/4" /> <div className="mt-2 h-4 bg-gray-200 rounded w-1/2" /> </div> ); }
Progress Indicators: Show determinate progress
function ProgressBar({ progress }: { progress: number }) {
return (
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<motion.div
className="h-full bg-blue-600"
initial={{ width: 0 }}
animate={{ width: ${progress}% }}
transition={{ ease: "easeOut" }}
/>
</div>
);
}
- State Transitions
Toggle with smooth transition:
function Toggle({ checked, onChange }) {
return (
<button
role="switch"
aria-checked={checked}
onClick={() => onChange(!checked)}
className={ relative w-12 h-6 rounded-full transition-colors duration-200 ${checked ? "bg-blue-600" : "bg-gray-300"} }
>
<motion.span
className="absolute top-1 left-1 w-4 h-4 bg-white rounded-full shadow"
animate={{ x: checked ? 24 : 0 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>
</button>
);
}
- Page Transitions
Framer Motion layout animations:
import { AnimatePresence, motion } from "framer-motion";
function PageTransition({ children, key }) { return ( <AnimatePresence mode="wait"> <motion.div key={key} initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} transition={{ duration: 0.3 }} > {children} </motion.div> </AnimatePresence> ); }
- Feedback Patterns
Ripple effect on click:
function RippleButton({ children, onClick }) { const [ripples, setRipples] = useState([]);
const handleClick = (e) => { const rect = e.currentTarget.getBoundingClientRect(); const ripple = { x: e.clientX - rect.left, y: e.clientY - rect.top, id: Date.now(), }; setRipples((prev) => [...prev, ripple]); setTimeout(() => { setRipples((prev) => prev.filter((r) => r.id !== ripple.id)); }, 600); onClick?.(e); };
return ( <button onClick={handleClick} className="relative overflow-hidden"> {children} {ripples.map((ripple) => ( <span key={ripple.id} className="absolute bg-white/30 rounded-full animate-ripple" style={{ left: ripple.x, top: ripple.y }} /> ))} </button> ); }
- Gesture Interactions
Swipe to dismiss:
function SwipeCard({ children, onDismiss }) { return ( <motion.div drag="x" dragConstraints={{ left: 0, right: 0 }} onDragEnd={(_, info) => { if (Math.abs(info.offset.x) > 100) { onDismiss(); } }} className="cursor-grab active:cursor-grabbing" > {children} </motion.div> ); }
CSS Animation Patterns
Keyframe Animations
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
@keyframes spin { to { transform: rotate(360deg); } }
.animate-fadeIn { animation: fadeIn 0.3s ease-out; } .animate-pulse { animation: pulse 2s ease-in-out infinite; } .animate-spin { animation: spin 1s linear infinite; }
CSS Transitions
.card { transition: transform 0.2s ease-out, box-shadow 0.2s ease-out; }
.card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); }
Accessibility Considerations
/* Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } }
function AnimatedComponent() { const prefersReducedMotion = window.matchMedia( "(prefers-reduced-motion: reduce)", ).matches;
return ( <motion.div animate={{ opacity: 1 }} transition={{ duration: prefersReducedMotion ? 0 : 0.3 }} /> ); }
Best Practices
-
Performance First: Use transform and opacity for smooth 60fps
-
Reduce Motion Support: Always respect prefers-reduced-motion
-
Consistent Timing: Use a timing scale across the app
-
Natural Physics: Prefer spring animations over linear
-
Interruptible: Allow users to cancel long animations
-
Progressive Enhancement: Work without JS animations
-
Test on Devices: Performance varies significantly
Common Issues
-
Janky Animations: Avoid animating width , height , top , left
-
Over-animation: Too much motion causes fatigue
-
Blocking Interactions: Never prevent user input during animations
-
Memory Leaks: Clean up animation listeners on unmount
-
Flash of Content: Use will-change sparingly for optimization
Resources
-
Framer Motion Documentation
-
CSS Animation Guide
-
Material Design Motion
-
GSAP Animation Library