1. Web Design
  2. HTML/CSS
  3. Animation

How to Build a JavaScript Popup Modal From Scratch

In this tutorial we’ll learn how to build flexible popup modals (windows) with HTML, CSS, and JavaScript–without using a framework like Bootstrap!
Scroll to top

In this tutorial we’ll learn how to build JavaScript modals (popup windows) without using a framework like Bootstrap, or a third party library. We’ll build the whole thing from scratch, giving us complete control over how it works and looks.

JavaScript Modal Demo

Here’s the demo we’ll be creating:

Please accept marketing cookies to load this content.

1. Begin With the Page Markup

First, we’ll create a modal. To do this, we’ll add the .modal class and a unique ID to a container. Next, we’ll specify the dialog by setting a .modal-dialog element as the direct child of the .modal. The dialog will hold the modal content. This can be any kind of content like text, images, lightboxes, user notifications/alerts, etc.

“A pop-up (or modal) is a small UI element that will appear in the foreground of a website, usually triggered as a prompt for the user to do something” – Adi Purdila

To open a modal, we’ll need any element with the data-open attribute (normally a button). The value of this attribute should be the ID of the desired modal.

By default, a modal will close if we click outside its boundaries or when the Esc key is pressed. But, we can also close it if we click on any element with the data-close attribute (normally a button).

Initially, the modal will appear/disappear with a fade effect. But we have the ability to adjust the animation effect of the dialog via the data-animation attribute. The value of this attribute which has to be added to the .modal can be any of the following values:

  • slideInOutDown
  • slideInOutTop
  • slideInOutLeft
  • slideInOutRight
  • zoomInOut
  • rotateInOutDown
  • mixInAnimations

We’ll have a closer look at these values in an upcoming section.

For now, let’s get familiar with the markup needed for representing a single modal: 

1
<button type="button" class="open-modal" data-open="modal1">...</button>
2
3
<div class="modal" id="modal1">
4
  <div class="modal-dialog">
5
    <header class="modal-header">
6
      ...
7
      <button class="close-modal" aria-label="close modal" data-close></button>
8
    </header>
9
    <section class="modal-content">...</section>
10
    <footer class="modal-footer">...</footer>
11
  </div>
12
</div>

2. Define Some Basic Styles

With the markup ready, we’ll set up a few CSS variables and reset styles:

1
:root {
2
  --lightgray: #efefef;
3
  --blue: steelblue;
4
  --white: #fff;
5
  --black: rgba(0, 0, 0, 0.8);
6
  --bounceEasing: cubic-bezier(0.51, 0.92, 0.24, 1.15);
7
}
8
9
* {
10
  padding: 0;
11
  margin: 0;
12
}
13
14
button {
15
  cursor: pointer;
16
  background: transparent;
17
  border: none;
18
  outline: none;
19
  font-size: inherit;
20
}

Next, we’ll horizontally and vertically center the page contents. Plus, we’ll give some styles to the button responsible for opening the modal:

1
/*CUSTOM VARIABLES HERE*/
2
3
body {
4
  display: flex;
5
  align-items: center;
6
  justify-content: center;
7
  height: 100vh;
8
  font: 16px/1.5 sans-serif;
9
}
10
11
.btn-group {
12
  text-align: center;
13
}
14
15
.open-modal {
16
  font-weight: bold;
17
  background: var(--blue);
18
  color: var(--white);
19
  padding: .75rem 1.75rem;
20
  margin-bottom: 1rem;
21
  border-radius: 5px;
22
}

At this point, we’ll focus our attention on the modal styles.

Each modal will have the following characteristics:

  • It’ll be full-screen with a fixed position. That said, it'll look like an overlay that covers the entire window’s width and height.
  • It’ll have a dark background color.
  • It’ll be hidden by default.
  • The dialog will be horizontally and vertically centered.
1
/*CUSTOM VARIABLES HERE*/
2
3
.modal { 
4
  position: fixed;
5
  top: 0;
6
  left: 0;
7
  right: 0;
8
  bottom: 0;
9
  display: flex;
10
  align-items: center;
11
  justify-content: center;
12
  padding: 1rem;
13
  background: var(--black);
14
  cursor: pointer;
15
  visibility: hidden;
16
  opacity: 0;
17
  transition: all 0.35s ease-in;
18
}

The dialog will have a maximum width and a maximum height. Its height will be 80% of the window height. In cases where its height exceeds that value, a vertical scrollbar will appear:

1
/*CUSTOM VARIABLES HERE*/
2
3
.modal-dialog {
4
  position: relative;
5
  max-width: 800px;
6
  max-height: 80vh;
7
  border-radius: 5px;
8
  background: var(--white);
9
  overflow: auto;
10
  cursor: default;
11
}

As a last thing, we’ll define a few straightforward styles for the individual content sections:

1
/*CUSTOM VARIABLES HERE*/
2
3
.modal-dialog > * {
4
  padding: 1rem;
5
}
6
7
.modal-header,
8
.modal-footer {
9
  background: var(--lightgray);
10
}
11
12
.modal-header {
13
  display: flex;
14
  align-items: center;
15
  justify-content: space-between;
16
}
17
18
.modal-header .modal-close {
19
  font-size: 1.5rem;
20
}
21
22
.modal p + p {
23
  margin-top: 1rem;
24
}

3. Toggle JavaScript Modal

A page can have more than one JavaScript modal. But as already discussed earlier, all modals will initially be hidden.

Open JavaScript Modal

Similarly, a page can have more than one open triggers (elements with the data-open attribute). Each time a trigger is clicked, the associated modal should become visible with a fade-in animation. Remember the data-open attribute value has to match the ID of a modal.

Here’s the script which reveals a modal:

1
const openEls = document.querySelectorAll("[data-open]");
2
const isVisible = "is-visible";
3
4
for(const el of openEls) {
5
  el.addEventListener("click", function() {
6
    const modalId = this.dataset.open;
7
    document.getElementById(modalId).classList.add(isVisible);
8
  });
9
}

And the relevant CSS classes:

1
.modal {
2
  visibility: hidden;
3
  opacity: 0;
4
  transition: all 0.35s ease-in;
5
}
6
7
.modal.is-visible {
8
  visibility: visible;
9
  opacity: 1;
10
}

Close JavaScript Modal

With our implementation, only a single modal can appear at a time (this code doesn’t support nested modals). As mentioned in the markup section above, there are three methods available for hiding it with a fade-out effect.

Let’s recap.

Firstly by clicking on the custom [data-close] element which is located inside the modal:

1
const closeEls = document.querySelectorAll("[data-close]");
2
const isVisible = "is-visible";
3
4
for (const el of closeEls) {
5
  el.addEventListener("click", function() {
6
    this.parentElement.parentElement.parentElement.classList.remove(isVisible);
7
  });
8
}

Secondly by clicking on everything outside of the modal:

1
const isVisible = "is-visible";
2
3
document.addEventListener("click", e => {
4
  if (e.target == document.querySelector(".modal.is-visible")) {
5
    document.querySelector(".modal.is-visible").classList.remove(isVisible);
6
  }
7
});

In this case, the modal (overlay) behaves as a giant close button. For this reason, we gave it cursor: pointer.

Lastly by pressing the Esc key:

1
const isVisible = "is-visible";
2
3
document.addEventListener("keyup", e => {
4
  if (e.key == "Escape" && document.querySelector(".modal.is-visible")) {
5
    document.querySelector(".modal.is-visible").classList.remove(isVisible);
6
  }
7
});

Now is a good time to look at what we’ve created so far:

Please accept marketing cookies to load this content.

The modal looks pretty good! Notice that each time we click on an open trigger, only the corresponding modal loads.

Let’s take it one step further and examine some ideas for animating its dialog.

4. Add Dialog Animations

As we said earlier, the default behavior of the modal is to fade in and fade out. But there’s the option to adjust the animation effect of the popup.

I’ve already created a bunch of animation effects that you can use as an alternative to the fade effect. To do this, just pass the data-animation="yourDesiredAnimation" attribute to the .modal.

For example, if you want the dialog to appear with a slide animation from left to right, you’ll need the slideInOutLeft effect.

Behind the scenes, there are two rules that accomplish this desired animation:

1
/*CUSTOM VARIABLES HERE*/
2
3
[data-animation="slideInOutLeft"] .modal-dialog {
4
  opacity: 0;
5
  transform: translateX(-100%);
6
  transition: all 0.5s var(--bounceEasing);
7
}
8
9
[data-animation="slideInOutLeft"].is-visible .modal-dialog {
10
  opacity: 1;
11
  transform: none;
12
  transition-delay: 0.2s;
13
}

Check the modal with this type of animation here:

Please accept marketing cookies to load this content.

You can check the rest of the animations by looking at the CSS tab of the final demo project. Depending on the complexity of the animations, I’ve used either CSS transitions or animations to build them. 

I’ve also made use of the cubic-bezier() function for setting the timing function for all transitions. If you don’t like the bounce effect that produces, feel free to change it to something smoother via the --bounceEasing CSS variable.

Have a look at the final demo with all the animation effects here:

Please accept marketing cookies to load this content.

5. Full-Screen Modal

As a nice extra, this tutorial has been updated with a full-screen modifier version.

Let’s go a step further and make our modal full-screen. For this scenario, I’ve created a new demo to make it easier for you to understand the styles. In terms of the modal’s functionality, nothing will change. 

Please accept marketing cookies to load this content.

This time the dialog won’t have a maximum width and height; it’ll expand to cover the viewport size. Also, it’ll be a flex container, and its footer will stick at the bottom regardless of the modal content.

If you want to learn more on how to create a sticky footer, have a look at this tutorial.

Going even further, let’s look at an alternative implementation of our full-screen JavaScript modal where we place an image on the left side and some text on the right side (on mobile they’re stacked). In addition, there isn’t any header or footer, and the close button sits as an absolutely positioned element at the right top.

Please accept marketing cookies to load this content.

Play with it by resizing the browser window to see how it adapts to various screens.

Of course, you’re welcome to build on this simple modal layout and make its grid more flexible using a framework like Tailwind CSS or Bootstrap. You can also turn the modal into a lightbox by showing an image grid, as we’ve done in this tutorial.

Conclusion

That’s it, folks! During this tutorial, we learned how to build custom animated JavaScript modal dialogs without relying on any front-end framework. I hope you enjoyed the final result, and building it helped refresh your front-end skills. 

Bear in mind that we haven’t considered accessibility, so if you want to enhance this demo that certainly could be the next step.

As always, thanks for reading!

More Popup Reading

Advertisement