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(linearleft topleft bottomfrom(#ccc), to(#fff));
    
-moz-background-clippadding;
    -
webkit-background-clippadding;
    
background-clippadding;

    
positionfixed;
    
left50%;
    
top50%;
    
z-index10;

    
displaynone;
    
width400px;
    
margin: -150px 0 0 -200px;
    
padding15px;

    
border15px solid rgba(20601000.7);

    -
moz-border-radius10px;
    -
webkit-border-radius10px;
    
border-radius10px;

    -
webkit-box-shadow0 0 20px rgba(3030300.7);
    -
moz-box-shadow0 0 20px rgba(3030300.7);
    
box-shadow0 0 20px rgba(3030300.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 */
positionfixed;
left50%;
top50%;
z-index10;

/* 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 */
width400px;

/* 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-clippadding;
-
webkit-background-clippadding;
background-clippadding;

/* Give Safari 4 (hopefully other browsers as well soon :) users a gradient background "image" */
background-image: -webkit-gradient(linearleft topleft bottomfrom(#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! */
border15px solid rgba(3030300.7);

/* Border radius should be self-explanatory but if not it gives the element's border a radius */
-moz-border-radius10px;
-
webkit-border-radius10px;
border-radius10px;

/* Finally we give it some shadow, I recommend you Google CSS box shadow for more info on this one */
-webkit-box-shadow0 0 20px rgba(3030300.7);
-
moz-box-shadow0 0 20px rgba(3030300.7);
box-shadow0 0 20px rgba(3030300.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;
    
displaynone;

    
positionfixed;
    
left0;
    
top0;
    
z-index5;

    
width100%;
    
height100%;
}

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 (
0numDivsi++) {
            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 (
0numAsi++) {
            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 
0numDivsi++) {
            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

Bookmark this Article

  • del.icio.us
  • Digg
  • Furl
  • Google
  • Technorati
  • Ma.gnolia
  • Blinklist
  • Blogmarks
  • Rojo
  • Stumbleupon

Comments

  1. Ags

    Published Friday 14th of August 2009

    Very good!

  2. Sam

    Published Wednesday 28th of April 2010

    If you add the following to the code it will work in ie6

    _position: absolute;
    
  3. Accessible What?

    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?

  4. Andreas

    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?

  5. Matt

    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-alignleft;
    }


    html body.js-enabled div.modal {
                    
    background-color#fff;
                    
    background-image: -webkit-gradient(linearleft topleft bottomfrom(#ccc), to(#fff));
                    
    -moz-background-clippadding;
                    -
    webkit-background-clippadding;
                    
    background-clippadding;

                    
    positionfixed;
                    
    left50%;
                    
    top50%;
                    
    z-index10;

                    
    displaynone;
                    
    width400px;
                    
    margin: -150px 0 0 -200px;
                    
    padding15px;

                    
    border15px solid rgba(20601000.7);

                    -
    moz-border-radius10px;
                    -
    webkit-border-radius10px;
                    
    border-radius10px;

                    -
    webkit-box-shadow0 0 20px rgba(3030300.7);
                    -
    moz-box-shadow0 0 20px rgba(3030300.7);
                    
    box-shadow0 0 20px rgba(3030300.7);
                }


    #modal-overlay {
                    
    background#000;
                    
    opacity.5;
                    
    displaynone;

                    
    positionfixed;
                    
    left0;
                    
    top0;
                    
    z-index5;

                    
    width100%;
                    
    height100%;
                }

    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>

  6. roboteich

    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.

  7. AL

    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.

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...

    Details

  • 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...

    Details

  • Vibrate

    This plug-in makes any element you want "vibrate" every now and then. Can be used in conjunction with blink for maximum annoyance!

    Details

More Plug-ins

Recent Comments

  1. Coneelolo on "Phu Quoc, Sihanouk Ville, Koh Chang, Koh Wai & Koh Mak"

    For the help please use http://www.google.com

  2. AL on "Accessible, Stylish Modal Windows With Modern CSS"

    @Matt - What exactly isn't working? "Not working" ...

  3. roboteich on "Accessible, Stylish Modal Windows With Modern CSS"

    This is hardly accessible. Screen reader focus is...

Page cached. Loaded in: 0.007 second(s).
Last DB change: 2010-09-08 19:34:43
Last file change: 2010-08-12 15:31:16
Cache created: 2010-09-09 11:46:43