Modals: What? Why? How? HTML, CSS, JS

February 27th, 2021

What?

As you're scrolling down a site, the content fades and a popup appears. That's a modal.

When you click 'delete' on a file, and a dialog box pops up to ask if you're really sure you want to delete it. Your screen is disabled and you can't do anything until you deal with the message in the window. That's a modal.

Most simply, modals have two parts: a base to disable the page content and a popup window.

The popup has a message or requires some action. It can be a login screen, a reminder to save your work, a request to sign up for an email list, a cookies notification, etc. It demands your attention by hiding the page. It blocks your whole workflow until you deal with it.

Modals can be a bit controversial and are best used sparingly.

Why?

Why use a modal? Popups? How annoying!

They can be helpful when used sparingly, when you really want the attention of the user. For example, if someone clicks on the trash button, you may want to confirm their intention before you go ahead and delete their data. They're useful when you want to block the forward momentum of the user, like until a password is entered and confirmed.

opine-modal.png

Read Me First

Before sending your modal out into world, let's check a few things to make it nice.

  1. Clear and Concise: Make sure to have a clear title, which states the purpose of the modal. Add any descriptive text as necessary. Make actions clear and easy to follow through on. Basically, make sure the user knows why the modal has appeared, and can make it go away as quickly and easily as possible.

  2. Size is Important: If it's too big, users may think a new page has opened up and try to close the whole tab. If it's too small it could be mistaken for an ad. Beware of modals on mobile devices. The smaller size can make it difficult to display nicely and interact with easily.

  3. Make it accessible:

    1. Keyboard: The escape key should close the modal, tab / enter keys can be used to select buttons. When the modal is open, non-modal content should not be keyboard accessible.

    2. All the normal accessibility best practices apply as well (color contrast, alt text for media, screen reader access, etc)

Keeping these things in mind, let's dive in and create a simple modal.

How?

HTML, CSS, JavaScript

Let's begin with a simple HTML template.

We'll need 4 things:

  • a div to wrap the modal

    • with an id so we can easily target it with our JavaScript and CSS

  • a div for the overlay to make the page go dark

    • with an id for the same reasons as above

  • another div for the modal window

    • with a class for styles

  • and we'll use a button to display our modal window

    • I put an id on it to make it clear what I'm selecting in my JavaScript

index.html

<button id="display-modal">Click Me</button>

<div class="hide-me" id="modal">
  <div id="overlay">
    <div class="modal-window">
      <h2>I am a modal popup!</h2>
    </div>
  </div>
</div>

HTML

CSS for overlay and modal window

We'll have three distinct styles.

The Overlay

The background color should make it clear the the page is disabled. A translucent gray can be a good choice, because it doesn't completely hide the page. Users can still see the content, even though they can't interact with it, so they know they have't been sent to a different site. By setting the position to fixed, and all the sides to 0, we make it cover the whole page with no gaps.

The Modal Window

It is important mainly that the background color contrasts with the overlay. It should be front and center on the page, clearly focusing the user's attention.

Hide Me

The third class is the one we'll toggle to display or hide the modal content.

styles.css

 #overlay {
  background-color: hsla(170, 5%, 15%, 0.8);
  position: fixed;
  top: 0;
  left:0;
  right: 0;
  bottom: 0;
}

.modal-window {
  width: 400px;
  height: 300px;
  background-color: #fff;
  margin: 100px auto 0;
}

/* Hide the Modal Window */
.hide-me {
  display: none;
}
CSS

JavaScript for Action

First, we need to select the HTML elements we want to interact with: our modal display button and the div containing our modal. Then, we create a simple function which will toggle the hide-me class on and off our modal container when the button is clicked.

Note: Element.classList returns an object. Read more about Element.classList

const modal = document.querySelector('#modal')
const displayModalButton = document.querySelector('#display-modal')

function displayModal() {
  if (modal.classList[0] === 'hide-me') {
    modal.classList.remove('hide-me') 
  } else {
    modal.classList.add('hide-me')
  }
}

displayModalButton.addEventListener('click', displayModal)

JavaScript

We want our modal to close when the user clicks outside the window, hits the Escape key, or performs some action in the modal window. Let's implement this by adding event listeners on the window to react to clicks and the Escape key. We'll also add buttons to our modal window, and event listeners on them. We'll write functions to toggle the hide-me class and close the modal when those events happen.

index.html

<!-- Modal -->
    <div class="hide-me" id="modal">
      <div id="overlay">
        <div class="modal-window">
          <div class="modal-title">
            <h2>I am a modal popup!</h2>
          </div>

          <!-- Modal Action Buttons -->
          <div class="modal-actions">
            <button class="action">Confirm!</button>
            <button class="action">Cancel</button>
          </div>
        </div>
      </div>
    </div>
HTML

Notice, we use document.querySelectorAll() to select our buttons. This method returns a NodeList of elements, which we can iterate over with forEach to put an event listener on each button.

index.js

...
const actionButtons = document.querySelectorAll('.action')

// function to close the modal when escape is pressed
const closeOnEscape = (e) => {
  if (e.key === 'Escape') {
    modal.classList.add('hide-me')
  }
}

// function to close the modal when user click outside modal window
const closeOnClick = (e) => {
  if (e.target.id === 'overlay') {
    modal.classList.add('hide-me')
  }
}

function displayModal() {
  if (modal.classList[0] === 'hide-me') {
    modal.classList.remove('hide-me')

    // Add Event Listeners to window
    window.addEventListener('keyup', closeOnEscape)
    window.addEventListener('click', closeOnClick)

    // Add Event Listeners to action buttons
    actionButtons.forEach((button) =>
      button.addEventListener('click', () => modal.classList.add('hide-me'))
    )

JavaScript

Let's look over our JavaScript.

Two things stand out to me as potential issues.

  1. We've added event listeners on the window to handle closing our modal, but we haven't removed them once the modal has closed. This could create conflicts if we want to add more functionality to our site in the future.

  2. We're repeating this line of code a lot: modal.classList.add('hide-me')

I suggest we tidy up our JavaScript a bit by creating a closeModal function. Our function will have logic to clean up our window event listeners and toggle the hide-me class onto our modal div. Then, whenever we want to close our modal, we'll call this function.

...

const closeModal = () => {
  modal.classList.add('hide-me')
  window.removeEventListener('click', closeOnClick)
  window.removeEventListener('keyup', closeOnEscape)
}

const closeOnEscape = (e) => {
  if (e.key === 'Escape') {
    closeModal()
  }
}

const closeOnClick = (e) => {
  if (e.target.id === 'overlay') {
    closeModal()
  }
}

function displayModal() {
  if (modal.classList[0] === 'hide-me') {
    modal.classList.remove('hide-me')
    window.addEventListener('keyup', closeOnEscape)
    window.addEventListener('click', closeOnClick)
    actionButtons.forEach((button) =>
      button.addEventListener('click', () => closeModal())
    )
  } else {
    closeModal()
  }
}

displayModalButton.addEventListener('click', displayModal)

JavaScript

Some final notes

Normally we would want to perform some task with these action buttons, like make an api request with some data to save a new user to the database, etc. If you check out the finished code below you can see I've added some content to our example page and modal window as well as a minimal amount of styling and animation to smooth the transitions.

I hope this helps you on your way.

As always, I look forward to hearing all your questions and comments!

~Rebecca

Finished Files

Here are all the files for this example.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="styles.css" />
    <title>Super Simple Modal</title>
  </head>
  <body>
    <div class="nav-bar">
      <nav>
        <span class="fake-link">Home</span> |
        <span class="fake-link">About Us</span> |
        <span class="fake-link">Contact</span>
      </nav>
    </div>

    <div class="page">
      <div class="page-title">
        <h1>Modals are Useful</h1>
        <p>When used correctly and sparingly.</p>
        <div>
          <p>Click to see the modal window.</p>
          <button id="display-modal">Click Me</button>
        </div>
      </div>

      <!-- Content -->
      <div class="page-content">
        <div class="fake-content-box">
          <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
            eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
            ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
            aliquip ex ea commodo consequat.
          </p>
        </div>
        <div class="fake-content-box">
          <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
            eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
            ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
            aliquip ex ea commodo consequat.
          </p>
        </div>

        <div class="fake-content-box">
          <p>
            Duis aute irure dolor in reprehenderit in voluptate velit esse
            cillum dolore eu fugiat nulla pariatur.
          </p>
        </div>
        <div class="fake-content-box">
          <p>
            Duis aute irure dolor in reprehenderit in voluptate velit esse
            cillum dolore eu fugiat nulla pariatur.
          </p>
        </div>
      </div>
    </div>

    <!-- Modal -->
    <div class="hide-me" id="modal">
      <div id="overlay">
        <div class="modal-window">
          <div class="modal-title">
            <h2>I am a modal popup!</h2>
          </div>
          <div class="modal-content">
            <p>You can close me 3 ways:</p>
            <ul>
              <li>Press the Escape key</li>
              <li>Click outside this popup</li>
              <li>Click on one of the action buttons below</li>
            </ul>
          </div>
          <div class="modal-actions">
            <button class="confirm-button action">Confirm!</button>
            <button class="action">Cancel</button>
          </div>
        </div>
      </div>
    </div>

  </body>
  <script src="index.js"></script>
</html>
HTML

index.js

const modal = document.querySelector('#modal')
const displayModalButton = document.querySelector('#display-modal')
const actionButtons = document.querySelectorAll('.action')

const closeModal = () => {
  modal.classList.remove('fade-in')
  modal.classList.add('fade-out')
  window.removeEventListener('click', closeOnClick)
  window.removeEventListener('keyup', closeOnEscape)
  setTimeout(() => {
    modal.classList.add('hide-me')
    modal.classList.remove('fade-out')

  }, 500)

}

const closeOnEscape = (e) => {
  if (e.key === 'Escape') {
    closeModal()
  }
}

const closeOnClick = (e) => {
  if (e.target.id === 'overlay') {
    closeModal()
  }
}

function displayModal() {
  if (modal.classList[0] === 'hide-me') {
    modal.classList.remove('hide-me')
    modal.classList.add('fade-in')
    window.addEventListener('keyup', closeOnEscape)
    window.addEventListener('click', closeOnClick)
    actionButtons.forEach((button) =>
      button.addEventListener('click', () => closeModal())
    )
  } else {
    closeModal()
  }
}

displayModalButton.addEventListener('click', displayModal)
JavaScript

styles.css

/* Nav */
.nav-bar {
  text-align: right;
  margin: 10px 40px;
}

.fake-link {
  cursor: pointer;
  color: #666666;
  padding: 7px;
}

.fake-link:hover {
  color: #333;
}

/* Page Content */
button {
  border-radius: 5px;
  border: solid #666666 1px;
  padding: 10px;
  cursor: pointer;
  font-size: 1em;
}

.fake-content-box {
  max-width: 350px;
}

.page {
  max-width: 800px;
  margin: 20px auto ;

}

.page-content {
  margin-top: 20px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-evenly;
}

.page-title {
  text-align: center;
}

/* Added some simple animations to fade in and out */
@keyframes fadein {
	from {
		opacity:0;
	}
	to {
		opacity:1;
	}
}

@keyframes fadeout {
	from {
		opacity:1;
	}
	to {
		opacity:0;
	}
}

.fade-in{
  animation:fadein 1s;
}

.fade-out{
  animation:fadeout .5s;
}

/* Modal Styles */
#overlay {
  background-color: hsla(170, 5%, 15%, 0.8);
  position: fixed;
  top: 0;
  left:0;
  right: 0;
  bottom: 0;
}

.modal-window {
  width: 400px;
  height: 300px;
  border-radius: 5px;
  background-color: #fff;
  margin: 100px auto;
  padding: 10px;
}

@media screen and (max-width: 500px) {
  .modal-window {
    width: 250px;
    height: 250px;
  }
}

.modal-title {
  text-align: center;
}

.modal-actions {
  padding: 10px;
  text-align: center;
}

.confirm-button {
  background-color: #bdd6ca;
  margin-right: 7px;
}

/* Hide the Modal Window */
.hide-me {
  display: none;
}
CSS

© 2026 Rebecca Page