Modal Transition with Framer Motion and NEXT

Modal Transition with Framer Motion and NEXT

Well, we all have been wanted to make a customized Modal with ease but we get stuck in few places as we build it. So let's dive into creating a modal with some cool transition animation.

What are we going to use?

We will be building it from scratch so we need the following

  • NEXTjs
  • Framer motion
  • Typescript

Initializing the NEXTjs Project

To create a NEXT project type the following lines

npx create-next-app@latest --typescript
# or
yarn create next-app --typescript

Name it as whatever you like but I am gonna name it as simple-modal for uniformity. Then change directory by cd simple-modal. Then run

yarn add framer-motion
yarn dev

This will add framer motion to the project, next we can start working on it.

Setting up the workspace

Clear out all the pre-written template from the index.tsx. Create a Components folder and create a file called modal.tsx. Design your Modal component as you like. For this tutorial I have designed it in the following way.

import { FC } from "react";

const Modal: FC = () => {
  return (
    <div className="modal-backdrop">
      <div className="modal-container">
        <h1>Modal Header</h1>
        <p>
          Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro
          reprehenderit dolores iure facilis libero repellendus pariatur, totam
          voluptate magnam dolorem assumenda soluta. Repellendus praesentium,
          ducimus corporis ab odio dignissimos quam?
        </p>
        <div className="close">
          <div></div>
          <div></div>
        </div>
      </div>
    </div>
  );
};

export default Modal;

And the styling goes like this in the global.css file

.modal-backdrop {
  background: rgb(43, 43, 43);
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  position: fixed;
}

.modal-container {
  width: 25rem;
  height: 32rem;
  background-color: whitesmoke;
  border-radius: 20px;
  box-shadow: 5px 5px 10px #1e1e1e;
  display: flex;
  flex-direction: column;
  padding: 3rem;
  align-items: center;
  position: relative;
}

.modal-container h1 {
  font-size: 2.5rem;
  color: rgb(0, 138, 92);
}

.modal-container p {
  font-size: 1.25rem;
  color: rgb(95, 121, 1);
  line-height: 2rem;
}

.close {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 2rem;
  height: 2rem;
  cursor: pointer;
}

.close div {
  position: absolute;
  height: 1.5rem;
  width: 3px;
  background: rgb(255, 145, 1);
  border-radius: 10px;
  right: 50%;
  left: 50%;
}

.close div:first-child {
  transform: rotate(45deg);
}

.close div:last-child {
  transform: rotate(-45deg);
}

Then Import it in the index.tsx file to see your changes.

The modal should look as below

image.png

Setting up Toggling

The simplest way to toggle a Modal to open and close state is using useState.

So the we add useState to index.tsx for toggling and also a button as follows:

const [isOpen, setIsOpen] = useState(false);
  return (
    <div className="background">
      <Head>
        <title>Simple Modal</title>
        <meta name="description" content="Simple Modal" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <button className="button" onClick={() => setIsOpen(true)}>
        Open Modal
      </button>
      <Modal isOpen={isOpen} setIsOpen={setIsOpen} />
    </div>
  );

Also add some button styles.

.button {
  padding: 10px 15px;
  background: rgb(255, 136, 0);
  border-radius: 100px;
  color: ghostwhite;
  font-weight: 600;
  letter-spacing: 3px;
  font-size: 1rem;
  border: none;
}

Make some changes in Modal.tsx for render the modal conditionally.

interface Props {
  isOpen: boolean;
  setIsOpen: (state: boolean) => void;
}

const Modal: FC<Props> = ({ isOpen, setIsOpen }) => {
  return (
    <>
      {isOpen && (
        <div className="modal-backdrop">
          <div className="modal-container">
            <h1>Modal Header</h1>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro
              reprehenderit dolores iure facilis libero repellendus pariatur,
              totam voluptate magnam dolorem assumenda soluta. Repellendus
              praesentium, ducimus corporis ab odio dignissimos quam?
            </p>
            <div className="close" onClick={() => setIsOpen(false)}>
              <div></div>
              <div></div>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

Well our Modal is ready? Not Exactly as you can see the transition is very abrupt.

Initiating magic to our Modal

Well now we import motion framer-motion to our Modal component. We use the motion and AnimatePresence from framer-motion to create magic as follow:

  1. Use a prefix for our div component and change it to motion.div.
    <motion.div className="modal-backdrop">
    <motion.div className="modal-container">
     <h1>Modal Header</h1>
     <p>
       Lorem ipsum dolor sit amet, consectetur adipisicing elit. Porro
       reprehenderit dolores iure facilis libero repellendus pariatur,
       totam voluptate magnam dolorem assumenda soluta. Repellendus
       praesentium, ducimus corporis ab odio dignissimos quam?
     </p>
     <motion.div className="close" onClick={() => setIsOpen(false)}>
       <div></div>
       <div></div>
     </motion.div>
    </motion.div>
    </motion.div>
    
  2. Wrap the Conditional rendering of our Modal component in AnimatePresence.
    <AnimatePresence>
    {isOpen && (
     // Modal code
    )}
    </AnimatePresence>
    
  3. Add the animations and transitions. Create the variants that will govern the animations
const backdropVariant = {
  hidden: {
    opacity: 0,
  },
  visible: {
    opacity: 1,
    transition: {
      duration: 1,
      delayChildren: 0.2, // To delay the child animation
    },
  },
};

const modalVariant = {
  hidden: {
    y: "-100vh",
  },
  visible: {
    y: 0,
    transition: {
      type: "spring", // Transition type animation used is spring
      stiffness: 70, // Stiffness of spring
    },
  },
};

There are 2 variants

  1. Backdrop variant is for the background overlay for the modal
  2. The animation for the modal itself

Also the 2 states in the variants are

  • hidden which is initial state of the transition
  • visible -> the final state of the transiton

Finally pass the variants into the motion.div components like below:

<motion.div
          className="modal-backdrop"
          variants={backdropVariant}
          initial="hidden"
          animate="visible"
          exit="hidden"
        >
          <motion.div className="modal-container" variants={modalVariant}>
                 // Rest of the code as usual

Optional Close button Animation

In the Close button component i.e Cross pass add the whileHover property as follows:

<motion.div
  whileHover={{ rotate: 45 }}
  className="close"
  onClick={() => setIsOpen(false)}
>
  <div></div>
  <div></div>
</motion.div>

It will rotate the cross on Hover.

Finally

The Modal is ready with some smooth transition. Enjoy the modal and use it where ever you can.

Github Repository - https://github.com/lawlesx/simple-modal/

Demo Link

Happy Coding