Jan 25, 2025

Creating an Animated Sign Up Button

In this article, I'm rebuilding the animated sign-up button I've encountered in Emil Kowalski's Animations on the Web course.

Figure 1: The animated sign-up button in action.

The first time I saw this animation, I truly felt inspired.

The animation sticks to the button itself, not affecting the layout of the page. It's a great example of how animations can enhance user experience without being intrusive.

Now let's dive into building this button.

Button Structure

Our animation is built with React and Framer Motion Motion, a library that makes it easy to create animations in Web applications, especially with React.

The button has three different states, which are reflected by the changing content of the button: idle, loading and success

So, our basic structure without any animations would look like this:

tsx
type ButtonState = "idle" | "loading" | "success";

export const AnimatedSignUpButton = () => {
  const [buttonState, setButtonState] = useState<ButtonState>("idle");

  const buttonStates: {
      [key in ButtonState]: string | JSX.Element;
  } = {
      idle: "Sign up for newsletter",
      loading: (
          <div className="size-4 animate-spin">
              <Loader2 className={styles.icon} />
          </div>
      ),
      success: "Thank you!",
  }

  return (
      <button>
          {buttonStates[buttonState]}
      </button>
  );
}

Handle state changes

In order to change the content of the button, we add a click listener and a handleSubmit function.

This function changes the state from idle to loading and then to success after a short delay.

tsx
const handleSubmit = () => {
  if (buttonState !== "idle") {
      return;
  }

  setButtonState("loading");

  setTimeout(() => {
      setButtonState("success");
  }, 2000);

  setTimeout(() => {
      setButtonState("idle");
  }, 4000);
};

return (
  <button onClick={handleSubmit}>
      {buttonStates[buttonState]}
  </button>
);

Adding Animations

Next, we need to import the motion object from framer-motion.

Inside our button, we wrap the content in a motion.span component. This tells Motion that we want to animate this element.

We can then add animations to the button by setting the initial, animate, exit and transition properties. In our case, we want the elements to slide in from the top and slide out to the bottom.

To ensure that Motion triggers the animation when the button state changes, we need to add a key property to the motion.span component. This property should be unique for each state.

tsx
<button className={styles.button} onClick={handleSubmit}>
  <AnimatePresence mode="popLayout" initial={false}>
      <motion.span
          className={styles.span}
          key={buttonState}
          transition={{
            type: "spring",
            duration,
            bounce,
          }}
          initial={{
            opacity: 0,
            y: -25,
          }}
          animate={{
            opacity: 1,
            y: 0,
          }}
          exit={{
            opacity: 0,
            y: 25,
          }}
          >
            {buttonStates[buttonState]}
      </motion.span>
  </AnimatePresence>
</button>

And that's it! We've created a beautiful animated sign-up button 🎉

Playground

Here you can play around with the button and see how it behaves in different states. Drag the Duration and Bounce sliders to see how the animation changes.