The CSS that makes perfect modals
Well I searched long and hard for great cross-device modals, but came up short. I decided to whip up my own, and I have a good solution. But first let's go over what makes a perfect modal.
- Large modals need to scroll
- Modal should be fixed to the top of the browser viewport
- Show a darkened backdrop while the modal is open
As for #1, you may think this is not an issue, but it is the largest when combined with the #2 requirement. If we want to do fixed positioning, we can use position:fixed — they'll be firmly anchored according to x and y coordinates on the physical display.
As for the problem this creates on every laptop, tablet, and phone except the iPhone, if the modal has a form, the keyboard will be up, and the fixed positioning means that the modal won't scroll! Only the rest of the page will. You can't see anything scrolling but you can see the scrollbar moving. This is what happens on the vast majority of websites.
For iPhones, Apple took it upon themselves to 'fix' the issue. When the keyboard is up it temporarily unfixes the fixed elements. Not a terrible solution, however it's highly nonstandard and causes issues for things that aren't modals. So many websites use fixed positioning for modals that Apple decided that's the only use case to support.
I actually decided to scrap fixed positioning on mobile and go for absolute instead. This mimics iPhones but is cross platform.
Next, the "backdrop" has long since been solved by the same method. There's always a 100% height, 100% width element with position:fixed that is slightly transparent, with a high z-index. The problem lies within the 100% height part. On a mobile browser, the control bars and address bars can go away, changing the height. When this happens, the height of the backdrop is not recalculated in real time. This leads to parts of the web page not being covered by the backdrop. This can be recreated in all mobile operating systems.
This was a blast to come up with. First, the backdrop since it's easiest to explain. Starting over, I decided to wrap the header and body in a container called #page which will be the page content minus the modal. When the modal is called, I immediately give the body a black background, but it doesn't show through just yet. Then I transition the opacity of #page down to 3%. This has the effect of a backdrop, but it's guaranteed to not produce any artifacts. You're not covering the page any more, you're fading the entire contents!
As for the positioning of the modal, CSS animation can be tricky, but with the right markup it can be done. The requirement that the modal comes from the top is the problem. Setting the original position of the the bottom of the modal just above the browser viewport is always skirted around. In all or most existing solutions the modal simultaneously fades in and slides down from a position a fixed point above the target coordinate. This is a poor approximation of the designer's intent. The solution is not hard, once you wrap your head around it.
I created a container div for the modal. A div will naturally have the same height as its closest descendant with zero extra code. Then, in CSS I target the modal, setting it's initial translateY to -100%. The negative 100% will position the modal just above the container div because the percent is in terms of the parent's height, which is calculated from the child's height.
When the modal is called, I wait until it's rendered, then apply a class that overrides translateY to 0. The modal slides down.