Accessible, Stylish Modal Windows With Modern CSS
Published Wednesday 12th of August 2009
With this tutorial I will try to show you a way to do accessible, stylish modal windows (complete with an overlay-div) without using a single image and just a couple of div-elements.
Our goal is to create a generic modal that can be re-used and that has these basic features:
Modals should be hidden by default, except if the user has JavaScript disabled
The modal should be centered in the view port, even as the user scrolls
The modal should have those semi-transparent, rounded corners I've been seeing a lot of lately :)
There should (obviously) be an overlay behind the modal, preventing the user from interacting with anything behind it
Clicking outside the modal (i.e. on the overlay) should hide the overlay as well as all open modals (could be optional)
Clicking a link pointing to a modal (a[href=#login] for example) should open the modal and display the overlay
Don't use any images and keep it accessible!
You can check out the example here. We'll kick off simple with the HTML.
The HTML Code
All we need for the actual modal window styling is one div-element with the class 'modal'. The div should have an ID as well so we can style, script and link to it uniquely. The ID should explain what the modal is (#login or #sign-up for example).
<div class="modal" id="login">
<h2>Log in</h2>
... form code ...
</div>
The overlay will also need a div but since it's not useful without JavaScript we'll use JS to add it, keeping it unobtrusive.
The CSS
I'll use some modern CSS to center the modal as well as give it a shadow and rounded, transparent corners.
body.js-enabled div.modal {
background: #fff;
background-image: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#fff));
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding;
position: fixed;
left: 50%;
top: 50%;
z-index: 10;
display: none;
width: 400px;
margin: -150px 0 0 -200px;
padding: 15px;
border: 15px solid rgba(20, 60, 100, 0.7);
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
-moz-box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
}
Phew! quite a lot of code, but we'll break it up. As you can see I'm styling the entire modal under body.js-enabled. I add the 'js-enabled' class to the body-element using JavaScript so that I can style differently depending on whether the user has JavaScript enabled or not:
<body class="js-disabled">
<script type="text/javascript">
document.body.className = document.body.className.replace('js-disabled', 'js-enabled');
</script>
...
The Centering
We want this modal to stay in the middle even if the user scrolls so we'll be using position: fixed.
As you may or may not know our all-time favorite IE6 doesn't have a clue what position: fixed means but if you haven't stopped spending time on that monster yet it's about time :)
We'll use JavaScript to center the modal properly as it's displayed later on. Right now I'll just use a random number.
/* Here we position the top-left corner of the element in the absolute middle of the view port (50% 50%), in other words, it won't be centered just yet, its top-left corner will be in the center. You could add a _position: absolute; for IE unless you, like me, serve it with the Universal IE6 CSS in which case it won't ever see this code anyway */
position: fixed;
left: 50%;
top: 50%;
z-index: 10;
/* Could be anything you want, but we're going to divide it by two and use for the negative left-margin so if you change it make sure to change both */
width: 400px;
/* This, together with position fixed is the centering magic. We simply use negative margin to pull the element half of its width to the left, and "half" of its height to the top (I'm using a random number as we can't know the height of the modal without JS) */
margin: -150px 0 0 -200px;
The Semi-Transparent, Rounded Corners and Shadow
The rounded corners and transparent border will be super-easily accomplished using zero images but some of the latest (and some old) CSS:
/* We need to use background-clip so the border isn't drawn on top of the background but on its outside */
background: #fff;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding;
/* Give Safari 4 (hopefully other browsers as well soon :) users a gradient background "image" */
background-image: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#fff));
/* The only news here is the use of rgba(). It's much like rgb() except it takes a forth argument for the opacity. Brilliance! */
border: 15px solid rgba(30, 30, 30, 0.7);
/* Border radius should be self-explanatory but if not it gives the element's border a radius */
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
/* Finally we give it some shadow, I recommend you Google CSS box shadow for more info on this one */
-webkit-box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
-moz-box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
IE, of course, doesn't understand either rgba(), box-shadow or border-radius. But we're all working with progressive enhancement here, right?
Finish It Off With an Overlay
Like I mentioned earlier the overlay is nothing more than a div-element. We'll use JS to append it to the body-element a little further down, but we can put this CSS in place right now.
We'll simply position the overlay fixed in the top-left corner as well as give it 100% width and height. This way it will always cover the entire body-element.
The background and opacity can of course be tweaked to your liking. A nice touch is to use some sort of background-pattern. Preferably a semi-transparent PNG-image. I've seen some nice effects done this way.
#modal-overlay {
background: #000;
opacity: .2;
display: none;
position: fixed;
left: 0;
top: 0;
z-index: 5;
width: 100%;
height: 100%;
}
Finally, the JavaScript
We'll use JS to add the overlay-div as well as "hijax" all the links pointing to modals. I'll wrap all our code in a MyModal-object so as not to create any unnecessary global variables.
var MyModal = {
/**
* This function will be run 'onload'
**/
init: function () {
this.addOverlay();
this.hijaxModalLinks();
},
/**
* Adds the overlay-div and attaches a click-event that hides
* all the modals as well as the overlay itself
**/
addOverlay: function () {
var overlay = document.createElement('div');
overlay.id = 'modal-overlay';
document.body.appendChild(overlay);
// You may want to remove this bit if you don't want the user to be able to remove the modal at all
overlay.onclick = function () {
MyModal.hideAllModals();
overlay.style.display = 'none';
};
},
/**
* Looks for modals with IDs, then looks for links
* pointing to those IDs and hijaxes those links
**/
hijaxModalLinks: function () {
var divs = document.getElementsByTagName('div');
var numDivs = divs.length;
var modalIDs = [];
var i;
for (i = 0; i < numDivs; i++) {
if (divs[i].id && divs[i].className.indexOf('modal') != -1) {
modalIDs.push(divs[i].id);
}
}
var as = document.getElementsByTagName('a');
var numAs = as.length;
var modalIDsStr = modalIDs.join(',');
for (i = 0; i < numAs; i++) {
if (modalIDsStr.indexOf(as[i].getAttribute('href', false).substr(1)) != -1) {
as[i].onclick = function () {
var modal = document.getElementById(this.getAttribute('href', false).substr(1));
modal.style.display = 'block';
modal.style.marginTop = '-' + (modal.offsetHeight / 2) + 'px';
document.getElementById('modal-overlay').style.display = 'block';
return false;
};
}
}
},
/**
* Hides every div-element with a class of 'modal'
**/
hideAllModals: function () {
var divs = document.getElementsByTagName('div');
var numDivs = divs.length;
for (var i = 0; i < numDivs; i++) {
if (divs[i].className.indexOf('modal') != -1) {
divs[i].style.display = 'none';
}
}
}
};
The JS was more of a bonus and I realize in many cases it's a bit too limited. It doesn't support bringing up ajax:ed content or showing modals without actually clicking a link. If you're familiar with JS you shouldn't have any problem extending my example (or writing your own) though.
If there's interest I could perhaps expand on the JS a bit and add support for more features. Perhaps a jQuery plug-in would make sense?
Example
You can check out an example here. All the code is in the .htm-file.
Enjoy and let me know what you thinks!
- Tags
- Comments
- 7 comments
Post a Comment
Random jQuery Plug-ins
Live Search
Use this plug-in to turn a normal form-input in to a live ajax search widget. The plug-in displays any HTML you like in the results and the search-res...
Content Scroller
Use this plug-in to make a list of elements only show one at a time and every now and then scroll to the next one. The plug-in scrolls in infinty, sta...
Vibrate
This plug-in makes any element you want "vibrate" every now and then. Can be used in conjunction with blink for maximum annoyance!
Comments
Published Friday 14th of August 2009
Very good!
Published Wednesday 28th of April 2010
If you add the following to the code it will work in ie6
Published Thursday 17th of June 2010
Er - what part of this do you think is Accessible? Do you think this would work well with AT or is tabbing behind the dialog a feature?
Published Friday 25th of June 2010
@Accessible What?
That's a very valid point. But I've seen so many ultra-inaccessible modal windows that would fail even non-AT users with JS disabled that I just wanted to show how easy it is to at least make them unobtrusive.
How would you go about disabling keyboard-navigation of the elements behind the dialogue?
Published Thursday 12th of August 2010
I have tried to implement this, but for some reason it is not working. Can someone please help?
Here is the css:
#login,{
text-align: left;
}
html > body.js-enabled div.modal {
background-color: #fff;
background-image: -webkit-gradient(linear, left top, left bottom, from(#ccc), to(#fff));
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding;
position: fixed;
left: 50%;
top: 50%;
z-index: 10;
display: none;
width: 400px;
margin: -150px 0 0 -200px;
padding: 15px;
border: 15px solid rgba(20, 60, 100, 0.7);
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
-moz-box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
box-shadow: 0 0 20px rgba(30, 30, 30, 0.7);
}
#modal-overlay {
background: #000;
opacity: .5;
display: none;
position: fixed;
left: 0;
top: 0;
z-index: 5;
width: 100%;
height: 100%;
}
And here is the html:
<body class="js-disabled">
<script type="text/javascript">
document.body.className = document.body.className.replace('js-disabled', 'js-enabled');
</script>
<div id="gallery-container">
<div id="content">
<!-- BEGIN MODAL 1 -->
<div id="img-holder">
<a href="#login"><img src="images/ls-img1-thumb.jpg" /></a>
</div>
<div id="login" class="modal">
<h1>Title</h1>
<p>Nam vestibulum, arcu sodales feugiat consectetur, nisl orci bibendum elit, eu euismod magna sapien ut nibh. Donec semper quam scelerisque tortor dictum gravida. In hac habitasse platea dictumst. Nam pulvinar, odio sed rhoncus suscipit, sem diam ultrices mauris, eu consequat purus metus eu velit.</p>
<p>Proin metus odio, aliquam eget molestie nec, gravida ut sapien. Phasellus quis est sed turpis sollicitudin venenatis sed eu odio. Praesent eget neque eu eros interdum malesuada non vel leo. Sed fringilla porta ligula egestas tincidunt. Nullam risus magna, ornare vitae varius eget, scelerisque a libero. Morbi eu porttitor ipsum. Nullam lorem nisi, posuere quis volutpat eget, luctus nec massa. Pellentesque aliquam lacinia tellus sit amet bibendum. Ut posuere justo in.</p>
<center>
<img src="images/ls-img1.jpg" />
</center>
</div>
<!-- END MODAL 1 -->
</div>
</div><!-- END GALLERY-CONTAINER -->
<script type="text/javascript">
var MyModal = {
/**
* This function will be run 'onload'
**/
init: function () {
this.addOverlay();
this.hijaxModalLinks();
},
/**
* Adds the overlay-div and attaches a click-event that hides
* all the modals as well as the overlay itself
**/
addOverlay: function () {
var overlay = document.createElement('div');
overlay.id = 'modal-overlay';
document.body.appendChild(overlay);
// You may want to remove this bit if you don't want the user to be able to remove the modal at all
overlay.onclick = function () {
MyModal.hideAllModals();
overlay.style.display = 'none';
};
},
/**
* Looks for modals with IDs, then looks for links
* pointing to those IDs and hijaxes those links
**/
hijaxModalLinks: function () {
var divs = document.getElementsByTagName('div');
var numDivs = divs.length;
var modalIDs = [];
var i;
for (i = 0; i < numDivs; i++) {
if (divs[i].id && divs[i].className.indexOf('modal') != -1) {
modalIDs.push(divs[i].id);
}
}
var as = document.getElementsByTagName('a');
var numAs = as.length;
var modalIDsStr = modalIDs.join(',');
for (i = 0; i < numAs; i++) {
if (modalIDsStr.indexOf(as[i].getAttribute('href', false).substr(1)) != -1) {
as[i].onclick = function () {
var modal = document.getElementById(this.getAttribute('href', false).substr(1));
modal.style.display = 'block';
modal.style.marginTop = '-' + (modal.offsetHeight / 2) + 'px';
document.getElementById('modal-overlay').style.display = 'block';
return false;
};
}
}
},
/**
* Hides every div-element with a class of 'modal'
**/
hideAllModals: function () {
var divs = document.getElementsByTagName('div');
var numDivs = divs.length;
for (var i = 0; i < numDivs; i++) {
if (divs[i].className.indexOf('modal') != -1) {
divs[i].style.display = 'none';
}
}
}
};
</script>
<div id="modal-overlay"></div>
</body>
Published Friday 13th of August 2010
This is hardly accessible. Screen reader focus is not redirected when the window opens, so no user of a reader would be aware of it's presence.
Tab indexing doesn't even skip to the first form input in the new dialog.
Published Friday 13th of August 2010
@Matt - What exactly isn't working? "Not working" doesn't say very much about your problem. Start with the minimum. Remove all the code that isn't from the tutorial and see if that works. Then work your way forward.
@roboteich - You pointed out pretty much exactly what "Accessible What?" did. So you can check my reply to that. Regarding focusing the first form field; the form in the tutorial is just an example. Depending on what you show in the modal you'll want to do different things with it. I didn't want to clutter the code by adding anything too specific.