Creating React Animations With Motion

Master the art of smooth, production-ready animations in React. From spring physics to gesture-driven interactions, learn how Motion elevates user experience.

Introduction

Smooth, purposeful animations have become a hallmark of modern web applications. They guide user attention, provide feedback, and create memorable digital experiences. Motion (formerly known as Framer Motion) has emerged as the premier animation library for React, empowering developers to create sophisticated animations with declarative APIs that feel native to the React ecosystem.

Whether you're building marketing sites with elegant entrance animations, dashboards with interactive data visualizations, or complex applications with gesture-driven interfaces, Motion provides the tools you need to bring your designs to life. The library's physics-based approach means animations feel natural and responsive, avoiding the mechanical look that plagues duration-based animation systems.

In this comprehensive guide, you'll learn everything from basic installation to advanced techniques like shared element transitions and scroll-linked animations. By the end, you'll have the knowledge to implement production-ready animations that enhance rather than distract from your user experience. Our web development services team specializes in building React applications with polished, performant animations.

Getting Started With Motion

Before diving into complex animations, let's establish a solid foundation with the basics. Motion integrates seamlessly with React's component model, allowing you to animate elements with the same declarative patterns you already use for rendering UI.

Motion was created by Framer, the design tool company, and has since evolved into a standalone library used by production teams worldwide. Its philosophy centers on making complex animations accessible while still providing the power needed for sophisticated effects. The library's declarative API means you describe what you want to happen, not how to achieve it - Motion handles the complexity of interpolation, gesture tracking, and performance optimization behind the scenes.

The library has gained widespread adoption because it strikes a balance between ease of use and capability. You can create simple fade-in effects in a single line of code, or build complex orchestrated sequences with multiple elements animating in coordination. This flexibility makes Motion suitable for everything from landing pages built with Next.js to interactive dashboards in complex React applications. For teams building modern web applications, investing in animation expertise through our web development services can significantly improve user engagement and perceived performance.

Installing Motion

Getting started with Motion is straightforward. Install the package using your preferred package manager:

npm install motion
# or
yarn add motion
# or
pnpm add motion

The Motion library is distributed as a monorepo containing packages for different platforms. For React web applications, the main motion package includes everything you need. The library is tree-shakeable, meaning unused features won't bloat your bundle size - you only pay for what you import and use.

Import Motion components from the main package to begin animating your React components. The library follows modern JavaScript module conventions, making it compatible with all current build tools and bundlers.

Your First Motion Component
1import { motion } from 'motion';2 3function FadeInBox() {4 return (5 <motion.div6 initial={{ opacity: 0, y: 20 }}7 animate={{ opacity: 1, y: 0 }}8 transition={{ duration: 0.5 }}9 />10 );11}

Core Concepts

Understanding Motion's core concepts is essential for building any animation. These fundamentals apply whether you're creating simple fades or complex, orchestrated sequences. Once you grasp these building blocks, you'll find that more advanced features build naturally on this foundation.

The key concepts include motion components that wrap standard elements, motion values that track animation state efficiently, and transforms that create dynamic, derived animations. Each serves a specific purpose in the animation system, and understanding when to use each will make your code more performant and maintainable.

Motion Components

At the heart of Motion are motion components - drop-in replacements for standard HTML and SVG elements. By wrapping elements with Motion's component variants, you unlock powerful animation capabilities without changing your markup structure.

Motion provides component variants for all standard HTML elements: motion.div, motion.span, motion.button, motion.input, and more. For SVG, you'll find motion.path, motion.circle, motion.rect, and other SVG-specific components. Each accepts animation props like initial, animate, and transition that define how the element should move and change over time.

The beauty of this approach is its simplicity. You can convert a static element to an animated one by simply changing div to motion.div in your JSX. No refactoring required, no context providers to wrap - just immediate access to a rich animation system. This zero-config nature for simple use cases means you can start small and add complexity as needed.

Motion Component Examples
1// Motion components work with any element2import { motion } from 'motion';3 4function MotionComponents() {5 return (6 <>7 <motion.button animate={{ scale: 1.1 }}>8 Click Me9 </motion.button>10 11 <motion.svg12 viewBox="0 0 100 100"13 animate={{ rotate: 360 }}14 >15 <circle cx="50" cy="50" r="40" />16 </motion.svg>17 18 <motion.img19 src="/image.jpg"20 initial={{ opacity: 0 }}21 animate={{ opacity: 1 }}22 />23 </>24 );25}

Motion Values

Motion values provide a way to track animation state outside React's render cycle. This is crucial for performance because updating a motion value doesn't trigger re-renders - it only updates the DOM directly.

When you use regular React state for animation values, each change causes a component re-render, which can become expensive for complex animations or frequent updates. Motion values bypass this entirely, allowing smooth 60fps animations even on lower-powered devices. This is especially important for gesture tracking where values update dozens of times per second.

The useMotionValue hook creates a motion value that you can read and update programmatically. Combined with useTransform, you can create derived values that respond to other motion values, enabling powerful patterns like scroll-linked animations where one value (scroll progress) drives multiple visual effects.

Using Motion Values
1import { motion, useMotionValue, useTransform } from 'motion';2 3function MotionValueExample() {4 const x = useMotionValue(0);5 const opacity = useTransform(x, [-100, 0, 100], [0.2, 1, 0.2]);6 7 return (8 <motion.div9 drag="x"10 style={{ x, opacity }}11 dragConstraints={{ left: -100, right: 100 }}12 >13 Drag me14 </motion.div>15 );16}

Transforms and useTransform

The useTransform hook creates derived values that map one motion value to another. This enables powerful patterns like scroll-linked animations, parallax effects, and dynamic styling based on gesture position.

Transforms work by defining input ranges and corresponding output ranges. For example, you might map scroll position from 0-500 pixels to an opacity value from 0-1, creating a fade-in effect as the user scrolls. The mapping is continuous and performant, updating smoothly as the input value changes.

Common use cases include parallax headers where background elements move slower than foreground content, scroll-progress indicators that track reading position, and gesture-driven effects where element properties respond to drag position. The key advantage of transforms is their declarative nature - you define the relationship once, and Motion handles all the intermediate value calculations efficiently.

Spring Animations

Spring animations distinguish Motion from libraries that rely purely on duration-based easing. Instead of specifying how long an animation takes, you describe the physical properties of a spring - and Motion calculates the motion naturally.

This physics-based approach produces animations that feel organic and responsive. When you tap a button, it doesn't move with mechanical precision - it bounces slightly, settling into place with a natural feel that users associate with physical objects. Spring animations achieve this by simulating mass, tension, and friction rather than fixed duration and easing curves.

For developers, springs eliminate the need to fine-tune timing functions for different animation durations. A spring with consistent stiffness and damping will feel appropriate whether animating a small icon or a large modal. This consistency makes it easier to maintain a coherent animation language across your application.

Understanding Spring Physics

A spring animation is defined by three key parameters:

  • Stiffness: How resistant the spring is to being stretched or compressed. Higher values create snappier, more responsive animations that reach their target quickly. Values around 100-300 work well for most UI elements.

  • Damping: How quickly the spring loses energy. Lower values create more oscillation (bounciness), with the element overshooting and bouncing back. Higher values create overdamped animations that approach the target without bouncing. A damping of 10-30 typically provides a pleasant bounce effect.

  • Mass: The weight of the object being animated. Higher mass creates slower, more gradual movements that feel heavier. Most UI elements work well with the default mass of 1.

Experimenting with these parameters helps develop intuition. A button tap might use stiffness of 400 with damping of 20 for a quick, responsive feel. A card entering the screen might use stiffness of 100 with damping of 30 for a softer, more welcoming entrance.

Spring Configuration
1const springTransition = {2 type: "spring",3 stiffness: 300,4 damping: 25,5 mass: 16};7 8// Quick, snappy animation9const snappy = {10 stiffness: 500,11 damping: 1512};13 14// Soft, gentle animation15const gentle = {16 stiffness: 100,17 damping: 20,18 mass: 219};

Animation Variants

Variants are one of Motion's most powerful features. They let you define named animation states and let Motion handle the orchestration of transitions between them across multiple elements.

Instead of managing each element's animation timing individually, you define variants that describe what each state looks like. Motion then coordinates the timing across all elements that share those variants. This declarative approach makes complex multi-element animations manageable - you simply describe the desired end state, and Motion handles the coordination.

Variants are particularly valuable for list animations, page transitions, and any scenario where multiple elements need to animate in coordination. The staggerChildren and delayChildren properties allow you to create cascading animations where elements enter one after another, creating visually appealing sequences without manual timing calculations.

Defining Variants
1const containerVariants = {2 hidden: { opacity: 0 },3 visible: {4 opacity: 1,5 transition: {6 staggerChildren: 0.1,7 delayChildren: 0.28 }9 }10};11 12const itemVariants = {13 hidden: { y: 20, opacity: 0 },14 visible: {15 y: 0,16 opacity: 1,17 transition: {18 type: "spring",19 stiffness: 300,20 damping: 2021 }22 }23};
Using Variants
1import { motion } from 'motion';2 3function StaggeredList() {4 return (5 <motion.ul6 variants={containerVariants}7 initial="hidden"8 animate="visible"9 >10 {[1, 2, 3, 4, 5].map((item) => (11 <motion.li12 key={item}13 variants={itemVariants}14 >15 Item {item}16 </motion.li>17 ))}18 </motion.ul>19 );20}

Gesture Animations

Motion excels at creating interactive animations that respond to user gestures. From drag interactions to hover states, these animations make your interface feel alive and responsive. Gesture animations provide immediate visual feedback that helps users understand what's interactive and how their actions affect the interface.

The library handles the complexity of tracking touch and pointer events, mapping them to animation values, and updating the visual state smoothly. This allows you to focus on what animations should occur, not how to track and interpolate user input. Motion's gesture system is optimized for performance, ensuring smooth response even during rapid interactions.

Drag Interactions

The drag prop enables intuitive drag-and-drop interactions. Combined with constraints and snap points, you can create sophisticated swipeable interfaces like carousels or sortable lists.

Motion handles the complexity of tracking pointer movement across the document, calculating deltas, and applying them as transforms. The dragConstraints prop limits movement to a defined area, while dragElastic controls how much "give" the element has at the constraint boundaries. For swipeable interfaces, you can use drag momentum and snap points to create satisfying interactions where elements settle into predefined positions.

Common patterns include swipeable cards in todo apps, draggable handles for resizing, reorderable list items, and carousel controls. The drag system works with both mouse and touch input, making it suitable for both desktop and mobile interfaces. When implementing drag interactions, consider adding visual feedback through whileDrag props that change scale, shadow, or cursor to indicate the draggable nature of the element.

Drag with Constraints
1import { motion } from 'motion';2 3function DraggableCard() {4 return (5 <motion.div6 drag7 dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}8 dragElastic={0.1}9 whileDrag={{ scale: 1.05, cursor: "grabbing" }}10 whileHover={{ cursor: "grab" }}11 >12 Drag me around13 </motion.div>14 );15}

Hover and Tap Effects

The whileHover and whileTap props create immediate visual feedback for user interactions. These subtle animations improve perceived responsiveness and make interfaces feel polished.

Hover effects typically include scale changes, brightness adjustments, or shadow enhancements that indicate an element is interactive. Tap effects (whileTap) provide feedback when users press down, usually through scale reduction or color changes. Motion handles the transition smoothly, avoiding the jarring snap that occurs with CSS :active states.

Accessibility is an important consideration for gesture animations. Motion automatically respects the prefers-reduced-motion media query, reducing or eliminating animations for users who have requested less motion. You can also use the useReducedMotion hook to conditionally disable animations or provide alternative experiences for these users. This built-in respect for accessibility preferences means your animations enhance the experience without excluding users who need reduced motion.

Interactive Button
1function InteractiveButton() {2 return (3 <motion.button4 whileHover={{ scale: 1.05 }}5 whileTap={{ scale: 0.95 }}6 whileFocus={{ boxShadow: "0 0 0 3px rgba(66, 153, 225, 0.5)" }}7 style={{8 background: "#4299e1",9 padding: "12px 24px",10 border: "none",11 borderRadius: 8,12 color: "white"13 }}14 >15 Click Me16 </motion.button>17 );18}

Scroll-Linked Animations

Motion's scroll tracking hooks enable animations that respond to scroll position. Use useScroll to track scroll progress and useTransform to map that progress to animation values.

The useScroll hook returns a motion value representing scroll progress, which you can then transform into visual properties. Common patterns include parallax headers where background content moves slower than scroll, fade-in effects that trigger as elements enter the viewport, and progress indicators that track reading position through an article.

WhileMotion components provide another approach to scroll-linked animations. These components only animate when they're visible in the viewport, making entrance animations simple to implement. By combining scroll tracking with transforms, you can create sophisticated effects like elements that rotate as you scroll past, images that scale based on scroll velocity, or navigation bars that transform based on scroll position. These effects, when used judiciously, create memorable scrolling experiences that distinguish modern web applications.

Layout Animations

One of Motion's most impressive capabilities is animating layout changes smoothly. When elements move in the DOM, Motion can automatically animate them to their new positions - no complex calculation required.

Traditional approaches to layout animations require calculating old and new positions, then animating between them manually. Motion handles this automatically through its layout animation system. When an element's position changes due to DOM changes, state updates, or responsive resizing, Motion detects the change and animates the element to its new position and size.

This capability is particularly valuable for responsive designs where elements reflow at different breakpoints, list reordering where items move to new positions, and modal transitions where content expands to fill the screen. The automatic nature of layout animations means you can focus on your application's logic rather than animation implementation.

LayoutId and Shared Elements

The layoutId prop creates "shared element transitions" - when two elements share the same layoutId, Motion automatically animates between their positions and sizes. This enables seamless transitions like expanding a card into a detail view.

Shared element transitions create continuity between different views, helping users understand the relationship between states. When a user taps a card and it expands to fill the screen, the shared element transition maintains visual connection, showing that the detail view is an expansion of the card they just tapped. This pattern is common in photo galleries, product listings, and news feeds.

Real-world use cases include list-to-detail transitions where a list item morphs into a full detail view, grid reorganizations where items animate smoothly to new positions when the grid changes, and modal presentations where content expands from a trigger button into an overlay. Motion handles all the complexity of calculating transforms between different element positions and animating between them smoothly.

Shared Element Transition
1import { motion } from 'motion';2 3function SharedElementExample() {4 return (5 <>6 <motion.div7 layoutId="card-1"8 initial={{ borderRadius: 20 }}9 className="card"10 >11 Click to expand12 </motion.div>13 14 {expanded && (15 <motion.div16 layoutId="card-1"17 initial={{ borderRadius: 20 }}18 className="expanded-card"19 >20 Expanded view with smooth transition21 </motion.div>22 )}23 </>24 );25}

AnimatePresence

AnimatePresence enables exit animations by keeping elements in the DOM until their exit animation completes. This component is essential for modals, toasts, list items, and any UI that shows and hides elements dynamically.

By default, React removes elements from the DOM immediately when they're no longer rendered, making exit animations impossible. AnimatePresence works around this by keeping exiting elements in the DOM, applying their exit animation, and only removing them once the animation completes. This enables smooth transitions for any element that can appear and disappear.

Common patterns include modal transitions where the overlay fades out while the modal shrinks, toast notifications that slide out when dismissed, list items that slide away when deleted, and page transitions where the old page fades out before the new one fades in. AnimatePresence works with the enter/update/exit lifecycle, allowing you to define distinct animations for each phase of an element's life.

Exit Animations with AnimatePresence
1import { motion, AnimatePresence } from 'motion';2 3function Modal({ isOpen, onClose }) {4 return (5 <AnimatePresence>6 {isOpen && (7 <>8 <motion.div9 initial={{ opacity: 0 }}10 animate={{ opacity: 1 }}11 exit={{ opacity: 0 }}12 onClick={onClose}13 className="overlay"14 />15 <motion.div16 initial={{ scale: 0.9, opacity: 0 }}17 animate={{ scale: 1, opacity: 1 }}18 exit={{ scale: 0.9, opacity: 0 }}19 className="modal"20 >21 Modal Content22 </motion.div>23 </>24 )}25 </AnimatePresence>26 );27}

Performance Best Practices

Even with a powerful library like Motion, animations can impact performance if not implemented thoughtfully. These best practices ensure your animations remain smooth and responsive across all devices and usage patterns.

Performance optimization for animations involves understanding what causes jank, avoiding unnecessary work, and leveraging hardware acceleration where possible. Motion handles much of this automatically, but being aware of the underlying principles helps you make better decisions about how to structure your animations. When building production React applications, our web development services team applies these optimization techniques to ensure buttery-smooth animations.

Optimizing Rendering

Motion values bypass React's render cycle for updates, which is a significant performance advantage. Understanding which properties trigger layouts versus compositor-only updates helps you write more efficient animations.

The browser's rendering pipeline consists of multiple phases: style calculation, layout, paint, and composite. Animating properties like width, height, or margin triggers layout recalculations, which are expensive. Animating transform, opacity, or filter properties only affects the compositor, which can run on the GPU independently of the main thread.

Motion is designed to optimize for compositor-only properties by default. When you animate transform or opacity, Motion ensures these updates happen efficiently. For other properties, consider whether you can achieve the same effect with transforms - for example, animating scale instead of width, or translateX instead of left/right positioning. When layout animations are necessary (such as with the layout prop), Motion handles the complexity efficiently, but being mindful of property choices leads to smoother animations.

Reducing Bundle Size

Motion is designed to be tree-shakeable, but understanding what you import affects your bundle size. For production applications, being intentional about imports can make a meaningful difference in initial load times.

The main motion package includes all features, but modern bundlers can eliminate unused code through tree-shaking. This means importing only what you use helps keep your bundle small. The library's modular architecture supports this pattern well, allowing you to import specific functions and components without pulling in the entire library.

For most applications, the default import provides a good balance of convenience and size. However, if bundle size is critical (such as for mobile-first experiences or markets with slow connections), consider auditing your imports and ensuring your bundler configuration supports ES module tree-shaking. The Motion documentation provides guidance on optimal import patterns for size-conscious applications.

Testing Animations

Testing animation code requires different approaches than testing static UI. Motion provides utilities that make it possible to test animations reliably in your test suite.

Jest and React Testing Library can work with Motion components, but animations introduce timing considerations that static UI doesn't. Tests that check for final states may need to account for animation duration, or use Motion's testing utilities to skip animations entirely during tests. This allows you to test component logic and rendering without waiting for animations to complete.

Visual regression testing tools like Chromatic or Percy can capture animations in different states, ensuring that animation changes don't inadvertently affect the visual appearance of your application. For critical user paths involving animations, consider adding explicit waits or using testing utilities that understand animation timing. The goal is to ensure animations enhance the experience without introducing flakiness or unexpected behavior in your test suite.

Practical Examples

Let's consolidate these concepts into complete, production-ready examples you can adapt for your own projects. These examples demonstrate how the individual concepts work together in realistic scenarios.

Each example includes variants for consistent animation definitions, proper exit handling for removed elements, and viewport visibility detection for performance optimization. These patterns represent common use cases in modern React applications and provide a starting point for your own implementations.

Animated Card Component
1import { motion } from 'motion';2 3const cardVariants = {4 hidden: { opacity: 0, y: 30 },5 visible: {6 opacity: 1,7 y: 0,8 transition: { type: "spring", stiffness: 300, damping: 20 }9 },10 hover: { y: -8, boxShadow: "0 20px 40px rgba(0,0,0,0.15)" },11 tap: { scale: 0.98 }12};13 14function AnimatedCard({ title, description }: CardProps) {15 return (16 <motion.div17 variants={cardVariants}18 initial="hidden"19 whileInView="visible"20 whileHover="hover"21 whileTap="tap"22 viewport={{ once: true }}23 style={{24 background: "white",25 borderRadius: 16,26 padding: 24,27 cursor: "pointer"28 }}29 >30 <h3>{title}</h3>31 <p>{description}</p>32 </motion.div>33 );34}
Staggered List Animation
1const container = {2 hidden: { opacity: 0 },3 visible: {4 opacity: 1,5 transition: {6 staggerChildren: 0.12,7 delayChildren: 0.38 }9 }10};11 12const item = {13 hidden: { x: -20, opacity: 0 },14 visible: {15 x: 0,16 opacity: 1,17 transition: {18 type: "spring",19 stiffness: 300,20 damping: 2521 }22 }23};24 25function StaggeredList({ items }: { items: string[] }) {26 return (27 <motion.ul28 variants={container}29 initial="hidden"30 animate="visible"31 style={{ listStyle: "none", padding: 0 }}32 >33 {items.map((item, i) => (34 <motion.li35 key={i}36 variants={item}37 style={{38 padding: 16,39 margin: 8,40 background: "#f7fafc",41 borderRadius: 842 }}43 >44 {item}45 </motion.li>46 ))}47 </motion.ul>48 );49}
Page Transition Component
1import { motion, AnimatePresence } from 'motion';2 3const pageVariants = {4 initial: { opacity: 0, x: 20 },5 animate: { opacity: 1, x: 0 },6 exit: { opacity: 0, x: -20 }7};8 9function PageTransition({ children, location }: PageProps) {10 return (11 <AnimatePresence mode="wait">12 <motion.div13 key={location.pathname}14 variants={pageVariants}15 initial="initial"16 animate="animate"17 exit="exit"18 transition={{ duration: 0.3 }}19 >20 {children}21 </motion.div>22 </AnimatePresence>23 );24}

When to Use Motion

Motion is powerful, but it's not the right tool for every animation. Understanding when to reach for Motion versus alternatives helps you make better architectural decisions and avoid over-engineering simple solutions.

For most React applications, Motion provides an excellent balance of capability and ease of use. Its declarative API integrates naturally with React's component model, and its physics-based animations produce results that feel superior to duration-based approaches. However, being thoughtful about when to use Motion (and when simpler approaches suffice) leads to better outcomes.

Consider using Motion when you need physics-based animations, gesture handling, layout animations, or orchestrated multi-element sequences. For simple hover effects or loading states, CSS transitions may be sufficient and add less overhead. For complex timeline-based animations with precise control, GSAP offers more power at the cost of simplicity.

Alternatives Comparison

Several animation libraries serve the React ecosystem. Each has distinct strengths that make it suitable for different scenarios:

  • CSS Transitions/Animations: Best for simple, one-off animations that don't need complex orchestration. Native browser support means no library overhead, but limited interactivity and no physics-based animations.

  • react-spring: Another physics-based animation library with a different API approach. Both libraries produce similar results; Motion's API may feel more natural to developers familiar with React patterns.

  • GSAP: The industry standard for complex, timeline-based animations. More powerful for intricate sequences and SVG path animations, but imperative API feels less natural in React.

Choose based on your specific needs: Motion for most React applications, CSS for simple interactions, GSAP for complex creative animations. Each tool has its place in a well-rounded development toolkit.

Integration With Next.js

Motion works well with Next.js, but there are specific considerations for the App Router architecture. Understanding these patterns ensures smooth integration with Next.js's server-first approach.

Motion components require client-side rendering, so they must be marked with 'use client' directive in Next.js App Router. This is a fundamental requirement - Motion manipulates the DOM directly, which only happens in the browser. You can create client components that use Motion and import them into server components without issues.

For page transitions in Next.js App Router, consider how AnimatePresence interacts with the router's page mounting behavior. The mode="wait" option ensures the old page exits before the new one enters, but this requires proper keying on the location pathname. Be aware of hydration concerns - animations that run on mount might cause mismatches between server and client renders if not handled carefully. Using dynamic import with ssr: false can help in specific scenarios where SSR conflicts with animation initialization.

Frequently Asked Questions

Ready to Elevate Your React Applications?

Smooth, purposeful animations transform good interfaces into great ones. Our team specializes in building performant, accessible animations that enhance user experience.

Sources

  1. Motion.dev React Documentation - Official Motion library documentation for React, covering all core features and APIs
  2. LogRocket: Creating React Animations with Motion - Step-by-step tutorial with practical code examples