Create a custom modal/popup with HTML, CSS, JQuery

Frameworks like Bootstrap or Foundation come with modal components, but if you aren't using such a framework, it can take a bit of time and effort to weed through all the code and boil down only the essentials you need for your modal.

You may also need to layer on your own styling, and will often run into CSS specificity issues.

Building your own modal might be the way to go.

Modals are made up of 4 core features:

  1. An overlay.
  2. The modal/popup itself.
  3. A button or link to trigger the modal.
  4. A button to close the modal.

So let's build one!

The first thing to keep in mind is accessibility and progressive enhancement. I like to avoid hiding things by default, because in case javascript fails, I still want the stuff in the modal to be findable on the page.

For the modal trigger, using an anchor tag with an href pointing to the modal will offer useful basic functionality. Later, we will add javascript to turn the anchor into a "show" button.

Then, include the modal somewhere on the page, wrap it in an overlay along with its content, and add a closing trigger of some sort. Here is what I came up with for the HTML:


<!-- Modal trigger - anchor by default -->
<a class="show-modal" href="#modal">Show Modal</a>

<!-- The modal, wrapped in an overlay -->
<div class="overlay">
  <div class="modal" id="modal">
    <!-- close trigger -->
    <div class="close">
      <span>X</span>
    </div>
    <!-- modal content -->
    <div class="modal-content">
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sed est vitae, iure quae nostrum cupiditate, debitis facere, qui sit optio quibusdam molestias veniam illo. Obcaecati ea aspernatur beatae fugit consequatur!</p>
    </div>
  </div>
</div>

Next, I just came up with some basic styling for when javascript is inactive.


* {
  box-sizing: border-box;
}

.modal {
  position: relative;
  padding: 20px;
  width: 50%;
  background: #f2f2f2;
}

.close {
  display: none;
  position: absolute;
  top: 0;
  right: 0;
  padding: 10px;
  background: #f2456f;
}
.close:hover {
  cursor: pointer;
}

I didn't add any styling for the overlay. By default, it just acts as a non-styled container. I also hid the close trigger, since it doesn't need to be showing if javascript is not functioning.

Time to enhance!

I included Modernizr, so I could layer on CSS which is scoped through the .js class on the <html> tag of the document. This way, these styles will only apply when javascript is up and running.

First, we need to write some CSS to make the overlay 100% of the height and width of the browser, along with semi-transparency. We also need to make sure it is hidden by default by using display: none;

(note: While you are working on the styling of the overlay and modal, it's easiest to leave out display: none; You can add it later when working on the javascript functionality.)

Then, we need to show the close button inside the modal.

And finally, we need to position the modal absolutely within the overlay. We will fine tune the positioning later in the javascript.

Here are these CSS enhancements:


.js .overlay {
  display: none; /* comment this line out when styling :) */
  position: fixed;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  z-index: 10; /* may vary */
  background: rgba(0,0,0,0.5); /* could also include a 1px fallback png for older browsers */
}
.js .modal {
  position: absolute;
  top: 20px;
  left: 50%;
}
.js .close {
  display: block;
}

Finally we can add in the javascript. I'll be using jQuery for its simplicity and cross-browser support.

The goal with the javascript is two-fold:

  1. To hide and show the modal.
  2. To position/size the modal based on the browser window.

Since the modal is contained inside the overlay, a simple show trigger on the anchor and a hide trigger on the close element will take care of the first objective by hiding/showing the overlay. (note: I also included e.preventDefault(); on the anchor)

For positioning and sizing the modal, we need to grab the dimensions of the window when we click the anchor trigger. From there, I set the modal to 50% width of the window, and used a negative margin equal to half of the width value in order to center it, since the left value in the CSS is equal to 50%.

There are other centering options available, but this negative margin technique will work back through IE8.

Here is the javascript I came up with:


var $modal = $('.modal'),
    $overlay = $('.overlay'),
    $showModal = $('.show-modal'),
    $close = $('.close');
    
/*show modal and set dimensions based on window */
$showModal.on('click', function(e){
  e.preventDefault();
  
  var windowHeight = $(window).height(),
      windowWidth = $(window).width(),
      modalWidth = windowWidth/2;
  
  $overlay.show();
  $modal.css({
    'width' : modalWidth,
    'margin-left' : -modalWidth/2
  });
});
/*close on click of 'x' */
$close.on('click', function(){
  $overlay.hide();
});
/* close on click outside of modal */
$overlay.on('click', function(e) {
  if (e.target !== this) return;
  $overlay.hide();
});

Here is the final result:

There are some other resources on the web which provide lightweight, framework-agnostic modals with varying degrees of extra functionality, styling, and CSS3 enhancements. Here are a few: