Building a Reusable MotionDiv Component for Next.js Applications

Building a Reusable MotionDiv Component for Next.js Applications

Learn how to create a flexible MotionDiv component that works seamlessly with Next.js server-side rendering while maintaining clean and reusable animation code.

When building modern interfaces with Motion and Next.js, animations quickly become a core part of the user experience. From page transitions and hover effects to interactive dashboards and marketing websites, it's common to reach for motion.div repeatedly throughout your application.

While Motion is incredibly flexible, repeatedly importing and configuring motion components can lead to duplicated code and inconsistent animation patterns.

A reusable MotionDiv component provides a simple abstraction that keeps your animations consistent while preserving the full power of Motion.

The Problem

A typical implementation often looks something like this:

import { motion } from "motion/react";

export default function Card() {
  return (
    <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
      Content
    </motion.div>
  );
}

This works perfectly, but over time you may start noticing a few drawbacks:

  • Repeated Motion imports throughout the codebase.
  • Inconsistent animation patterns between components.
  • More boilerplate when creating custom animated primitives.
  • Harder maintenance as the project grows.
  • No centralized place for animation defaults or future enhancements.

These issues are small at first, but become increasingly noticeable in larger applications.

Creating a Reusable MotionDiv

Let's create a reusable wrapper component that behaves exactly like a normal motion.div.

"use client";

import { HTMLMotionProps, motion } from "motion/react";

import { cn } from "@/lib/utils";

interface MotionDivProps extends HTMLMotionProps<"div"> {
  className?: string;
}

const MotionDiv = ({ children, className, ...motionProps }: MotionDivProps) => {
  return (
    <motion.div className={cn(className)} {...motionProps}>
      {children}
    </motion.div>
  );
};

export default MotionDiv;

The component is intentionally simple. It doesn't change how Motion works—it simply creates a reusable foundation that can evolve with your application.

Why This Pattern Works

Client Component

Motion animations require a browser environment to run correctly.

Using:

"use client";

ensures the component executes on the client where Motion can access the DOM and perform animations.

Full TypeScript Support

Because the component extends:

HTMLMotionProps<"div">;

you automatically inherit every prop supported by Motion.

This includes:

  • initial
  • animate
  • exit
  • variants
  • whileHover
  • whileTap
  • whileInView
  • transition

and many more.

You get full autocomplete, type safety, and future compatibility with Motion updates.

Better Reusability

Rather than importing Motion throughout the application, components can rely on a shared abstraction.

This becomes especially useful when building:

  • Design systems
  • Component libraries
  • SaaS products
  • Marketing websites
  • Internal dashboards
  • Large-scale React applications

Common Usage Examples

Basic Animation

import MotionDiv from "@/components/MotionDiv";

export default function Hero() {
  return (
    <MotionDiv
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      transition={{ duration: 0.5 }}
    >
      Welcome
    </MotionDiv>
  );
}

Hover Effects

import MotionDiv from "@/components/MotionDiv";

export default function Card() {
  return (
    <MotionDiv
      whileHover={{ scale: 1.05 }}
      whileTap={{ scale: 0.95 }}
      className="rounded-xl border p-6"
    >
      Interactive Card
    </MotionDiv>
  );
}

Variants

const variants = {
  hidden: {
    opacity: 0,
    y: 20,
  },
  visible: {
    opacity: 1,
    y: 0,
  },
};

export default function Section() {
  return (
    <MotionDiv variants={variants} initial="hidden" animate="visible">
      Animated Section
    </MotionDiv>
  );
}

Each example demonstrates how the wrapper remains flexible while providing a consistent API across the application.

MotionDiv vs motion.div

Choosing between a reusable abstraction and the raw Motion component depends on the context.

  • Shared UI Components — Consistency across the application.
  • Design Systems — Centralized animation API.
  • Large Applications — Easier maintenance and refactoring.
  • Custom Animation Libraries — Reusable foundation.

For most production applications, the reusable wrapper becomes increasingly valuable as the codebase grows.

Extending the Pattern

Once you have a reusable MotionDiv, you can build additional motion primitives on top of it.

MotionButton

"use client";

import { HTMLMotionProps, motion } from "motion/react";

import { cn } from "@/lib/utils";

interface MotionButtonProps extends HTMLMotionProps<"button"> {
  variant?: "primary" | "secondary";
}

export default function MotionButton({
  children,
  className,
  variant = "primary",
  ...props
}: MotionButtonProps) {
  return (
    <motion.button
      whileHover={{ scale: 1.03 }}
      whileTap={{ scale: 0.97 }}
      className={cn(
        "rounded-lg px-4 py-2",
        variant === "primary" ? "bg-black text-white" : "bg-neutral-200",
        className
      )}
      {...props}
    >
      {children}
    </motion.button>
  );
}

MotionSection

"use client";

import MotionDiv from "./MotionDiv";

export default function MotionSection({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <MotionDiv
      initial={{
        opacity: 0,
        y: 30,
      }}
      whileInView={{
        opacity: 1,
        y: 0,
      }}
      viewport={{
        once: true,
      }}
      transition={{
        duration: 0.5,
      }}
    >
      {children}
    </MotionDiv>
  );
}

By building small reusable primitives like these, you create a consistent animation system that can be shared throughout the application.

Benefits in Real Projects

After adopting reusable motion components, you'll typically notice several improvements:

  • Less repeated code.
  • Better developer experience.
  • Consistent animation behavior.
  • Easier refactoring.
  • Centralized animation updates.
  • Improved maintainability.
  • Faster onboarding for new contributors.

The abstraction is small, but the long-term benefits can be significant.

Conclusion

A reusable MotionDiv component is a small abstraction that can have a meaningful impact on the maintainability of a Next.js application.

By wrapping Motion's functionality inside a dedicated component, you create a consistent API, improve developer experience, and establish a foundation for reusable animation primitives.

As your project grows, this pattern makes it easier to standardize animations, reduce duplication, and build richer interactive experiences without scattering animation logic throughout the codebase.