Modals and Drawers

Written by Hamilton Cline
Last updated on April 4, 2019, 12:40 pm

What's a Modal?

Modal is basically the term for any popup on screen. Usually, you're forced to do something with the modal because everything else will be blocked out. So a modal is a popup that stops the user and forces them to interact.


In this tutorial, we'll be making liberal use of a concept from a previous tutorial called Data-Activate. You'll probably want to familiarize yourself with that concept before pushing into this one.

Covering the Screen

Let's start with some very basic structure.

<button type="button" class="btn solid main full" data-activate="#activate-1">Open Modal</button> <div id="activate-1" data-deactivate="#activate-1"></div> #activate-1.active { position:fixed; top:0; left:0; width:100%; height:100%; background-color:rgba(0,0,0,0.7); z-index:20; }

So we're starting off with something simple. We simply cover the whole screen with an element. We also give ourselves a z-index to make sure our element is in front of everything else that might be z-indexed in our design. Careful with z-indexes. Try to stick to low numbers. Don't go into the hundreds or thousands. That way leads to number wars. Most of your regular design should stick below 10, and this thing can be placed firmly above that with a number as simple as 20.

So let's make a class for this concept. In fact, let's call everything that covers the screen a .modal from now on. Even if it isn't designed as a popup. If it has a dark back, or covers the screen, it's a .modal.

Basic Structure

<div class="modal" id="activate-2"> <div class="modal-back" data-deactivate="#activate-2"></div> <div class="modal-popup"> <div class="modal-body">Modal</div> </div> </div> <button type="button" class="btn solid main full" data-activate="#activate-2">Open Modal</button>

So, let's add a new element called modal-back, and move our background color and our data-deactivate to that. Why? Because then we can have another element in front of it that doesn't close the modal on click. If you just have the whole modal close on click, then you can't interact with any of its children.

Next let's add our visible child, and call it a popup, since we're going to be designing a number of kinds of modals, popup being just one. And then, for now, let's a place a visible element with content inside of that.


.modal { position:fixed; top:0; left:0; width:100%; height:100%; z-index:20; pointer-events:none; opacity:0; } .modal.active { pointer-events:initial; opacity:1; }

Our modal no longer has any color on it. That will be on the modal back. But it does have a default opacity of 0. Some people like to use display:none here to remove the element from grabbing clicks. But you can't animate the display property, and we can simply remove click grabbing with the pointer-events:none property.


.modal-back { position:absolute; top:0; left:0; width:100%; height:100%; background-color:rgba(0,0,0,0.7); }

There's the background color. Notice that although this element also fills its parent, just like the modal did, we do not give it a position value of fixed. Don't put fixed elements inside fixed elements. Our back simply gets position absolute, since its parent is already fixed.

Centered Child

.modal-popup { position:absolute; top:50%; left:50%; transform:translate(-50%,calc(-50% + 2em)); width:400px; max-width:calc(100% - 4em); max-height:calc(100% - 4em); } .modal.active .modal-popup { transform:translate(-50%,-50%); }

Our modal-popup will get the centered child css trick. How does that work? Well it uses an intriguing difference between the percentages of placements and the percentages of translates. You see, top left bottom and right placement values when given a percentage, all calculate that value based on their parent's size. But when transform translate calculates a percentage, it's based on the child element's own size. This means we can do the real math of centering, which is moving over half of a parent's size, and then moving back half of the child's size.

We're also giving it a size, and a max size. That way it shouldn't get too crazy big unless we do something wrong. We'll set ourselves up for a transition animation, but offsetting our initial transform using calc and a couple of ems.

.modal-body { background-color:white; padding:1em; }

And there's just a simple modal-body.

Modal Popup

<div class="modal animated" id="activate-3"> <div class="modal-back" data-deactivate="#activate-3"></div> <div class="modal-popup rounded"> <div class="modal-header"><h1>header</h1></div> <div class="modal-body">Content</div> <div class="modal-footer nav-mobile"> <ul class="flex-parent"> <li class="flex-child"></li> <li class="flex-none"><a href="#" data-deactivate="#activate-3">Close</a></li> </ul> </div> </div> </div>

So there's some new stuff in here, so let's break it down. First things first, there are other tutorials for things like Mobile Navigations, Grid and Flex, and Two Buttons. So check out some of those for some in depth things that are going to be glossed over here.

.modal.animated, .modal.animated .modal-popup { transition:all 0.3s; }

Let's add some animation to our modal. Let's animate it's opacity change, and let's animate the modal transform. Why 0.3s? What is that? It's 300 milliseconds. And it's generally considered the longest amount of time any UI animation should ever take. Longer is too long, shorter can be imperceivable. So 0.3s it is.

.rounded>:first-child { border-radius: 0.2em 0.2em 0 0; } .rounded>:last-child { border-radius: 0 0 0.2em 0.2em; }

Let's add rounded corners to our first and last children of a class called rounded. That way we can put any combination of header, footer, and body in here and the first and last will always look nice.

.modal-header, .modal-footer { background-color:var(--neutral-light-color); --header-height:2.5rem; height:var(--header-height); line-height:var(--header-height); position: relative; font-size:0.9em; }

Putting in the concept of header and footer is easy enough, and although something like these have already been designed in the mobile navigation tutorial, it's worth making these be slightly different. But let's do keep the same variable name for header-height for consistency's sake.

Modal Drawer

Making other styles of modals is now the task we can give ourselves. Other kinds of modals, such as a drawer can be made with relative ease, using the same overall concept, but changing the design of the inner element in the modal.

<div class="modal animated" id="activate-4"> <div class="modal-back" data-deactivate="#activate-4"></div> <div class="modal-drawer"></div> </div> .modal-drawer { width:200px; height:100%; position: absolute; background-color: white; transform: translateX(-100%); transition: all 0.3s; } .modal.active .modal-drawer.left { transform: translateX(0); }

Making a modal drawer involves an element with a fixed width or height placed on the side of the view. Using the transform translate css property, we can animate it off screen by default, and then pull it back to it's appropriate position once activated.

In fact, extracting this out to a simple set of tools for placing a drawer on any part of the screen is pretty easy.

Extending Drawers

<div class="modal animated" id="activate-5-1"> <div class="modal-back" data-deactivate="#activate-5-1"></div> <div class="modal-drawer left"></div> </div> <div class="modal animated" id="activate-5-2"> <div class="modal-back" data-deactivate="#activate-5-2"></div> <div class="modal-drawer right"></div> </div> <div class="modal animated" id="activate-5-3"> <div class="modal-back" data-deactivate="#activate-5-3"></div> <div class="modal-drawer bottom"></div> </div> <div class="modal animated" id="activate-5-4"> <div class="modal-back" data-deactivate="#activate-5-4"></div> <div class="modal-drawer top"></div> </div> <div> <button type="button" class="btn solid main full" data-activate="#activate-5-1">Left</button> <button type="button" class="btn solid main full" data-activate="#activate-5-2">Right</button> <button type="button" class="btn solid main full" data-activate="#activate-5-3">Bottom</button> <button type="button" class="btn solid main full" data-activate="#activate-5-4">Top</button> </div> .modal-drawer { --drawer-depth:200px; --drawer-breadth:100%; position: absolute; background-color: white; transition: all 0.3s; } .modal-drawer.right, .modal-drawer.left { top:0; width: var(--drawer-depth); height: var(--drawer-breadth); } .modal-drawer.top, .modal-drawer.bottom { left:0; height: var(--drawer-depth); width: var(--drawer-breadth); } .modal-drawer.left { left:0; transform: translateX(-100%); } .modal-drawer.right { right:0; transform: translateX(100%); } .modal-drawer.top { top:0; transform: translateY(-100%); } .modal-drawer.bottom { bottom:0; transform: translateY(100%); } .modal.active .modal-drawer.right, .modal.active .modal-drawer.left { transform: translateX(0); } .modal.active .modal-drawer.bottom, .modal.active .modal-drawer.top { transform: translateY(0); }

So what have we done here? Well for the height and width, we've defined a depth and breadth variable. These two values can be used on all of our orientations. We organize our statements into horizontal and vertical groups and then define each item depending on what it needs from the X or Y axis. It's not small necessarily, but now if we want a drawer on the left, we just give it a set of modal-drawer left classes.

Written by Hamilton Cline
Last updated on April 4, 2019, 12:40 pm