Considerations for Styling a Modal

Avatar of Chris Coyier
Chris Coyier on (Updated on )

A modal. A small box that pops up to tell you something important. How hard can it be? Wellllll. Medium hard, I’d say. There’s quite a few considerations and a few tricky things to get just right. Let us count the ways.

I typically plop a modal’s HTML before the closing </body> tag.

  <div class="modal" id="modal"></div>

</body>

</html>

That’s primarily for styling reasons. It’s easier to position the modal when you’re dealing with the page-covering body rather than an unknown set of parent elements, each potentially with their own positioning context.

How does that fair for screen readers? I’m not an accessibility expert, but I’ve heard modals are pretty tricky. Rob Dodson:

For anyone who has ever tried to make a modal accessible you know that even though they seem pretty innocuous, modals are actually the boss battle at the end of web accessibility. They will chew you up and spit you out. For instance, a proper modal will need to have the following features:

  • Keyboard focus should be moved inside of the modal and restored to the previous activeElement when the modal is closed
  • Keyboard focus should be trapped inside of the modal, so the user does not accidentally tab outside of the modal (a.k.a. “escaping the modal”)
  • Screen readers should also be trapped inside of the modal to prevent accidentally escaping

Rob suggests The Incredible Accessible Modal Window demo. I’ve also seen a recent example by Noah Blon and one by Nicolas Hoffman. Also, ARIA Live Regions are relevant here.

If you’re handling focus yourself, having the modal be at the bottom of the document seems acceptable.

Centering

We have a complete guide to centering in CSS.

One of my favorite tricks is in there. The one where you can center both vertically and horizontally without explicitly knowing the width or height:

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

This is great for modals, which are typically exactly centered and might have different versions of different widths. Heights are even more likely to change since height always related to the content inside.

If you are absolutely certain of the height and width of the modal, you could consider other centering methods. I bring that up because there is a chance of slightly-blurry text with transforms, so if you are having trouble with that, look into the other methods in the centering guide (e.g. negative margins).

Fixed?

Notice we used position: fixed;. The point of this is that a user could be scrolled down the page, trigger a modal, and have the modal be just as centered-and-visible as it would be if they weren’t scrolled.

I guess, in general, I’d consider fixed positioning fairly safe these days, even on “mobile”. But if you know you’re dealing with a pretty healthy population of very old mobiles, fixed positioning can be a problem and you might consider something like position: absolute; and forcing a scroll to the top of the page. Or something, I dunno; do testing.

Dealing with Width

On large screens, the typical modal look is not only centered but also of limited width (like the above screenshot).

.modal {

  /* other stuff we already covered */

  width: 600px;
  
}

That’s red flag territory. We got what we wanted on large screens, but we know there are plenty of screens out there that aren’t even 600px wide all the way across.

Easily fixable with max-width:

.modal {

  /* other stuff we already covered */

  width: 600px;
  max-width: 100%;
  
}

Dealing with Height

Setting a height is even more red flaggy. We know content changes! Plus, the transforms-centering technique is more than happy to cut off the top of the modal with no scrollbar to save you:

Setting a max will save us again:

.modal {

  /* other stuff we already covered */

  height: 400px;
  max-height: 100%;
  
}

Dealing with Overflow

Now that we’re in the business of setting heights, we need to consider overflow. It’s tempting to use an overflow value right on the .modal itself, but there are two problems with that:

  • We might want some elements that don’t scroll
  • Overflow will cut off a box-shadow, which we may want

I’d suggest an inner container:

<div class="modal" id="modal">

  <!-- things that don't scroll -->

  <div class="modal-guts">

    <!-- things that scroll -->

  </div>

</div>

In order for the guts to scroll, it will need a height. There is a variety of possibilities here. One is to position it to cover the entire modal, and then add the overflow:

.modal-guts {

  /* other stuff we already covered */

  /* cover the modal */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

  /* spacing as needed */
  padding: 20px 50px 20px 20px;

  /* let it scroll */
  overflow: auto;
  
}

The Buttons

The point of a modal is to force an action before anything else can be done. If you aren’t forcing an action, consider UI other than a modal. There needs to be some kind of way out of the modal. Option buttons are common (i.e. “Delete” / “Cancel”). A close button is also common. Let’s give ours a close button.

Having a close button that is always visible seems smart, so the user can’t be in a state where it’s not visibly clear how to close the modal. That’s why we made the non-scrollable area.

Good styling is on you ;)

Dealing with the overlay

A modal is often accompanied by a full-screen-covering overlay. This is useful for a number of reasons:

  • It can darken (or otherwise mute) the rest of the screen, enforcing the “you need to deal with this before you leave” purpose of a modal.
  • It can be used to prevent clicks/interactions on stuff outside the modal.
  • It can be used as a giant close button. Or “cancel” or whatever the most innocuous action is.

Typical handling:

<div class="modal" id="modal">
  <!-- modal stuff -->
</div>
<div class="modal-overlay" id="modal-overlay">
</div>
.modal {

  /* stuff we already covered */

  z-index: 1010;

}
.modal-overlay {

  /* recommendation:
     don't focus on the number "1000" here, but rather,
     you should have a documented system for z-index and 
     follow that system. This number should be pretty
     high on the scale in that system.
  */
  z-index: 1000;

  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

}

Closed with a class (rather than open with a class)

I find it highly tempting to make the default .modal class be hidden by default, likely with display: none;. Then to open it, add an open class, like .modal.open { display: block; }.

See the display: block; there though? I’d consider that a problem. display: none; is quite useful because it hides the modal both visually and from assistive technology. It’s easier to apply it on top of an existing display value than it is to override with a guess of a value. Meaning your .modal could use display: flex; or display: grid; or whatever else is useful. Different variations of modals could use whatever they want without worry they’ll be reset to display: block;.

.modal {

  /* for example... */
  display: flex;  

}
.modal.closed {
 
  display: none;

}

Toggling Openness

Here’s the most basic possible way of opening and closing with what we have so far:

var modal = document.querySelector("#modal");
var modalOverlay = document.querySelector("#modal-overlay");
var closeButton = document.querySelector("#close-button");
var openButton = document.querySelector("#open-button");

closeButton.addEventListener("click", function() {
  modal.classList.toggle("closed");
  modalOverlay.classList.toggle("closed");
});

openButton.addEventListener("click", function() {
  modal.classList.toggle("closed");
  modalOverlay.classList.toggle("closed");
});

This does not deal with accessibility yet. Remember that’s a consideration we talked about above. Here’s a demo that deals with moving focus, trapping focus, and returning focus back from whence it came.

Style Considerations Demo

See the Pen Considerations for Styling a Modal by Chris Coyier (@chriscoyier) on CodePen.

What did I miss? Feel free to fork that thing and let me know.