Graphic Design

Add Smooth Animations to Your Next.js App with Framer Motion & Tailwind CSS

As a front-end developer, you know that a static user interface, no matter how functional, can sometimes feel lifeless. Users expect dynamic, responsive experiences, and adding smooth animations is key to delivering that polish. This tutorial will guide you through integrating Framer Motion and Tailwind CSS into your Next.js application to create professional, engaging micro-interactions and transitions that elevate your UI.

Juno School Web Development Full Course Thumbnail - showing code and design elements
Recommended Course on JunoWeb Development - Full Course in Hindi
View Course →

Why Animate Your UI? The Power of Micro-interactions

Animations are more than just visual flair; they are crucial for improving user experience. Subtle movements, known as micro-interactions, guide users, provide feedback, and make an application feel more intuitive and enjoyable. With Framer Motion, you can easily add these sophisticated touches. We will learn how to use Framer Motion to add micro-interactions like hover animations, smooth transitions, and fade-ins. These modern touches significantly enhance the user experience, making your application feel premium and responsive.

Imagine a button that subtly scales when hovered over, or a list of items that gracefully fades into view one by one. These aren't just cosmetic changes; they communicate functionality and delight the user. Combining Framer Motion's declarative API with Tailwind CSS's utility-first approach makes implementing these effects efficient and maintainable.

Setting Up Your Project with Framer Motion and Tailwind CSS

Before we dive into animations, let's ensure your Next.js project is set up with Tailwind CSS and Framer Motion. If you already have a Next.js project with Tailwind CSS configured, you can skip to step 3.

  1. Create a New Next.js Project:
    npx create-next-app@latest my-animated-app --typescript --eslint --tailwind --app
    Follow the prompts. For this tutorial, we'll use the App Router.
  2. Verify Tailwind CSS Setup: Next.js's setup command includes Tailwind CSS. Ensure your tailwind.config.js and globals.css files are correctly configured. You should see Tailwind directives in globals.css:
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
  3. Install Framer Motion: Add Framer Motion to your project dependencies:
    npm install framer-motion
    or
    yarn add framer-motion

Your project is now ready to begin adding dynamic animations. If you're looking to integrate other powerful tools with Next.js, consider learning how to connect Sanity CMS with Next.js 14 for a robust content management solution.

Your First Animation: Fading in Elements on Load with Framer Motion

Let's start with a classic: making an element fade in smoothly when it appears on the screen. This is a common effect for headings, paragraphs, and other UI components to give a polished entry.

Framer Motion provides the motion component, which is a wrapper around standard HTML or React components (like div, span, button, etc.) that enables animation capabilities. We'll use motion.div for our heading.

Here's how to make a heading fade in from slightly below its final position:

import { motion } from 'framer-motion';

export default function Home() {
  // Define animation variants
  const fadeInAnimationVariants = {
    initial: {
      opacity: 0,
      y: 10, // Start slightly below
    },
    animate: {
      opacity: 1,
      y: 0, // End at original position
      transition: {
        duration: 0.6,
        ease: "easeOut",
      },
    },
  };

  return (
    <div className="flex min-h-screen flex-col items-center justify-center p-24 bg-gray-900 text-white">
      <motion.h1
        className="text-5xl font-bold mb-4"
        variants={fadeInAnimationVariants}
        initial="initial"
        animate="animate"
      >
        Welcome to Juno School!
      </motion.h1>
      <motion.p
        className="text-xl"
        variants={fadeInAnimationVariants}
        initial="initial"
        animate="animate"
        transition={{ delay: 0.2 }} // Add a slight delay for the paragraph
      >
        Learn professional skills for free.
      </motion.p>
    </div>
  );
}

In this example, for the heading, we set the initial condition to an opacity of 0 and a y-position of 10. Then, in the animate prop, we set opacity to 1 and y to 0. Finally, we add a transition for a smooth effect. This creates a subtle fade-in-from-bottom animation. We also applied the same variants to a paragraph, adding a delay to its transition for a staggered effect.

Creating Interactive Hover and Tap Effects with Framer Motion

Micro-interactions truly shine when elements respond to user input. Framer Motion makes it simple to add interactive effects like scaling on hover or tap. Let's create an interactive button.

import { motion } from 'framer-motion';

export default function InteractiveButton() {
  return (
    <div className="flex min-h-screen flex-col items-center justify-center bg-gray-900 p-24">
      <motion.button
        className="px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-75"
        whileHover={{ scale: 1.1 }} // Scale up on hover
        whileTap={{ scale: 0.95 }}  // Scale down on tap
        transition={{ type: "spring", stiffness: 400, damping: 10 }}
      >
        Click Me!
      </motion.button>
    </div>
  );
}

Here, we've used a motion.button and set whileHover to scale it slightly to 1.1, making the button appear a little larger when the user hovers over it. For whileTap, we've scaled it to 0.95, giving it a slight compression effect when clicked. The transition prop with a "spring" type makes the animation feel natural and bouncy, a hallmark of excellent smooth animation techniques.

Orchestrating Animations with Staggering

When you have multiple elements, like items in a list or components in a hero section, animating them all at once can look clunky. Staggering introduces a slight delay between each element's animation, creating a pleasing cascade effect that feels organized and professional.

Framer Motion uses variants and a parent motion component to orchestrate these effects. The parent defines the overall animation, and its children inherit and apply their own delays.

import { motion } from 'framer-motion';

export default function StaggeredList() {
  const containerVariants = {
    hidden: { opacity: 0 },
    show: {
      opacity: 1,
      transition: {
        staggerChildren: 0.1, // Delay between each child's animation
      },
    },
  };

  const itemVariants = {
    hidden: { opacity: 0, y: 20 },
    show: { opacity: 1, y: 0 },
  };

  const listItems = ["Learn React", "Build Projects", "Get Certified", "Join Juno"];

  return (
    <div className="flex min-h-screen flex-col items-center justify-center bg-gray-900 p-24 text-white">
      <h2 className="text-3xl font-bold mb-6">Our Learning Path</h2>
      <motion.ul
        variants={containerVariants}
        initial="hidden"
        animate="show"
        className="list-disc list-inside text-xl space-y-2"
      >
        {listItems.map((item, index) => (
          <motion.li key={index} variants={itemVariants}>
            {item}
          </motion.li>
        ))}
      </motion.ul>
    </div>
  );
}

In this code, the containerVariants define a staggerChildren property, which tells Framer Motion to apply a 0.1-second delay between each child's animation. The itemVariants then define how each individual list item animates. This creates a smooth, cascading entrance for the list items, significantly enhancing the overall aesthetic and user perception of "modern touches" in the UI.

Putting It All Together: Animating a Full Hero Section with Framer Motion and Tailwind CSS

Now, let's combine all the techniques we've learned to create a fully animated hero section. We'll have text fading in, an image appearing with a delay, and an interactive button.

import { motion } from 'framer-motion';
import Image from 'next/image'; // Assuming Next.js Image component

export default function AnimatedHeroSection() {
  const containerVariants = {
    hidden: { opacity: 0 },
    show: {
      opacity: 1,
      transition: {
        staggerChildren: 0.15, // Stagger children slightly
      },
    },
  };

  const textVariants = {
    hidden: { opacity: 0, y: 20 },
    show: {
      opacity: 1,
      y: 0,
      transition: {
        duration: 0.6,
        ease: "easeOut",
      },
    },
  };

  const imageVariants = {
    hidden: { opacity: 0, scale: 0.8 },
    show: {
      opacity: 1,
      scale: 1,
      transition: {
        delay: 0.5, // Image appears after text
        duration: 0.8,
        ease: "easeOut",
      },
    },
  };

  return (
    <motion.section
      className="relative flex flex-col md:flex-row items-center justify-center min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 text-white p-8 md:p-16 overflow-hidden"
      variants={containerVariants}
      initial="hidden"
      animate="show"
    >
      {/* Background element for subtle animation */}
      <motion.div
        className="absolute inset-0 z-0 bg-blue-500 opacity-10 blur-3xl"
        animate={{
          scale: [1, 1.05, 1],
          rotate: [0, 5, -5, 0],
        }}
        transition={{
          duration: 10,
          repeat: Infinity,
          repeatType: "reverse",
          ease: "easeInOut",
        }}
      />

      <div className="relative z-10 flex flex-col md:w-1/2 text-center md:text-left mb-8 md:mb-0">
        <motion.h1
          className="text-5xl md:text-6xl font-extrabold mb-4 leading-tight"
          variants={textVariants}
        >
          Unlock Your Potential with Juno School
        </motion.h1>
        <motion.p
          className="text-lg md:text-xl mb-8 text-gray-300 max-w-md md:max-w-none mx-auto"
          variants={textVariants}
        >
          Explore a world of free, professional certificate courses designed for your career growth.
        </motion.p>
        <motion.button
          className="px-8 py-4 bg-purple-600 text-white font-bold rounded-full shadow-lg hover:bg-purple-700 transition-colors duration-300 self-center md:self-start"
          whileHover={{ scale: 1.05, boxShadow: "0px 10px 20px rgba(0,0,0,0.3)" }}
          whileTap={{ scale: 0.98 }}
          transition={{ type: "spring", stiffness: 300, damping: 15 }}
          variants={textVariants} // Use text variants for initial appearance
        >
          Start Learning Today
        </motion.button>
      </div>

      <motion.div
        className="relative z-10 md:w-1/2 flex justify-center items-center"
        variants={imageVariants}
      >
        <Image
          src="/hero-image.png" // Replace with your actual image path
          alt="Student learning on laptop"
          width={600}
          height={400}
          className="rounded-xl shadow-2xl"
        />
      </motion.div>
    </motion.section>
  );
}

In this comprehensive hero section, we've applied several Framer Motion concepts:

By using Framer Motion and Tailwind CSS, you can build not just functional, but also beautiful and engaging user interfaces in your Next.js applications. These techniques are part of a broader set of skills covered in Juno's Web Development Full Course in Hindi, where you can learn to master modern web technologies.

Ready to level up your career?

Join 5 lakh+ learners on the Juno app. Certificate courses in Hindi and English.

Get it onGoogle Play
Download on theApp Store