Menu Search
Jump to the content X X
Smashing Conf New York

We use ad-blockers as well, you know. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. upcoming SmashingConf Barcelona, dedicated to smart front-end techniques and design patterns.

Making Modal Windows Better For Everyone

To you, modal windows1 might be a blessing of additional screen real estate, providing a way to deliver contextual information, notifications and other actions relevant to the current screen. On the other hand, modals might feel like a hack that you’ve been forced to commit in order to cram extra content on the screen. These are the extreme ends of the spectrum, and users are caught in the middle. Depending on how a user browses the Internet, modal windows can be downright confusing.

Modals quickly shift visual focus from one part of a website or application to another area of (hopefully related) content. The action is usually not jarring if initiated by the user, but it can be annoying and disorienting if it occurs automatically, as happens with the modal window’s evil cousins, the “nag screen” and the “interstitial.”

However, modals are merely a mild annoyance in the end, right? The user just has to click the “close” button, quickly skim some content or fill out a form to dismiss it.

Well, imagine that you had to navigate the web with a keyboard. Suppose that a modal window appeared on the screen, and you had very little context to know what it is and why it’s obscuring the content you’re trying to browse. Now you’re wondering, “How do I interact with this?” or “How do I get rid of it?” because your keyboard’s focus hasn’t automatically moved to the modal window.

This scenario is more common than it should be. And it’s fairly easy to solve, as long as we make our content accessible to all through sound usability practices.

For an example, I’ve set up a demo of an inaccessible modal window2 that appears on page load and that isn’t entirely semantic. First, interact with it using your mouse to see that it actually works. Then, try interacting with it using only your keyboard.

Better Semantics Lead To Better Usability And Accessibility Link

Usability and accessibility are lacking in many modal windows. Whether they’re used to provide additional actions or inputs for interaction with the page, to include more information about a particular section of content, or to provide notifications that can be easily dismissed, modals need to be easy for everyone to use.

To achieve this goal, first we must focus on the semantics of the modal’s markup. This might seem like a no-brainer, but the step is not always followed.

Suppose that a popular gaming website has a full-page modal overlay and has implemented a “close” button with the code below:

<div id="modal_overlay">
  <div id="modal_close" onClick="modalClose()">
    X
  </div>
  …
</div>

This div element has no semantic meaning behind it. Sighted visitors will know that this is a “close” button because it looks like one. It has a hover state, so there is some visual indication that it can be interacted with.

But this element has no inherit semantic meaning to people who use a keyboard or screen reader.

There’s no default way to enable users to tab to a div without adding a tabindex attribute to it. However, we would also need to add a :focus state to visually indicate that it is the active element. That still doesn’t give screen readers enough information for users to discern the element’s meaning. An “X” is the only label here. While we can assume that people who use screen readers would know that the letter “X” means “close,” if it was a multiplication sign (using the HTML entity &times;) or a cross mark (&#x274c;), then some screen readers wouldn’t read it at all. We need a better fallback.

We can circumvent all of these issues simply by writing the correct, semantic markup for a button and by adding an ARIA label for screen readers:

<div id="modal_overlay">
  <button type="button" class="btn-close" id="modal_close" aria-label="close">
    X
  </button>
</div>

By changing the div to a button, we’ve significantly improved the semantics of our “close” button. We’ve addressed the common expectation that the button can be tabbed to with a keyboard and appear focused, and we’ve provided context by adding the ARIA label for screen readers.

That’s just one example of how to make the markup of our modals more semantic, but we can do a lot more to create a useful and accessible experience.

Making Modals More Usable And Accessible Link

Semantic markup goes a long way to building a fully usable and accessible modal window, but still more CSS and JavaScript can take the experience to the next level.

Including Focus States Link

Provide a focus state! This obviously isn’t exclusive to modal windows; many elements lack a proper focus state in some form or another beyond the browser’s basic default one (which may or may not have been cleared by your CSS reset). At the very least, pair the focus state with the hover state you’ve already designed:

.btn:hover, .btn:focus {
  background: #f00;
}

However, because focusing and hovering are different types of interaction, giving the focus state its own style makes sense.

.btn:hover {
  background: #f00;
}

:focus {
  box-shadow: 0 0 3px rgba(0,0,0,.75);
}

Really, any item that can be focused should have a focus state. Keep that in mind if you’re extending the browser’s default dotted outline.

Saving Last Active Element Link

When a modal window loads, the element that the user last interacted with should be saved. That way, when the modal window closes and the user returns to where they were, the focus on that element will have been maintained. Think of it like a bookmark. Without it, when the user closes the modal, they would be sent back to the beginning of the document, left to find their place. Add the following to your modal’s opening and closing functions to save and reenable the user’s focus.

var lastFocus;

function modalShow () {
  lastFocus = document.activeElement;
}

function modalClose () {
  lastFocus.focus(); // place focus on the saved element
}

Shifting Focus Link

When the modal loads, focus should shift from the last active element either to the modal window itself or to the first interactive element in the modal, such as an input element. This will make the modal more usable because sighted visitors won’t have to reach for their mouse to click on the first element, and keyboard users won’t have to tab through a bunch of DOM elements to get there.

var modal = document.getElementById('your-modal-id-here');

function modalShow () {
   modal.setAttribute('tabindex', '0');
   modal.focus();
}

Going Full Screen Link

If your modal takes over the full screen, then obscure the contents of the main document for both sighted users and screen reader users. Without this happening, a keyboard user could easily tab their way outside of the modal without realizing it, which could lead to them interacting with the main document before completing whatever the modal window is asking them to do.

Use the following JavaScript to confine the user’s focus to the modal window until it is dismissed:

function focusRestrict ( event ) {
  document.addEventListener('focus', function( event ) {
    if ( modalOpen && !modal.contains( event.target ) ) {
      event.stopPropagation();
      modal.focus();
    }
  }, true);
}

While we want to prevent users from tabbing through the rest of the document while a modal is open, we don’t want to prevent them from accessing the browser’s chrome (after all, sighted users wouldn’t expect to be stuck in the browser’s tab while a modal window is open). The JavaScript above prevents tabbing to the document’s content outside of the modal window, instead bringing the user to the top of the modal.

If we also put the modal at the top of the DOM tree, as the first child of body, then hitting Shift + Tab would take the user out of the modal and into the browser’s chrome. If you’re not able to change the modal’s location in the DOM tree, then use the following JavaScript instead:

var m = document.getElementById('modal_window'),
    p = document.getElementById('page');

// Remember that <div id="page"> surrounds the whole document,
// so aria-hidden="true" can be applied to it when the modal opens.

function swap () {
  p.parentNode.insertBefore(m, p);
}

swap();

If you can’t move the modal in the DOM tree or reposition it with JavaScript, you still have other options for confining focus to the modal. You could keep track of the first and last focusable elements in the modal window. When the user reaches the last one and hits Tab, you could shift focus back to the top of the modal. (And you would do the opposite for Shift + Tab.)

A second option would be to create a list of all focusable nodes in the modal window and, upon the modal firing, allow for tabbing only through those nodes.

A third option would be to find all focusable nodes outside of the modal and set tabindex="-1" on them.

The problem with these first and second options is that they render the browser’s chrome inaccessible. If you must take this route, then adding a well-marked “close” button to the modal and supporting the Escape key are critical; without them, you will effectively trap keyboard users on the website.

The third option allows for tabbing within the modal and the browser’s chrome, but it comes with the performance cost of listing all focusable elements on the page and negating their ability to be focused. The cost might not be much on a small page, but on a page with many links and form elements, it can become quite a chore. Not to mention, when the modal closes, you would need to return all elements to their previous state.

Clearly, we have a lot to consider to enable users to effectively tab within a modal.

Dismissing Link

Finally, modals should be easy to dismiss. Standard alert() modal dialogs can be closed by hitting the Escape key, so following suit with our modal would be expected — and a convenience. If your modal has multiple focusable elements, allowing the user to just hit Escape is much better than forcing them to tab through content to get to the “close” button.

function modalClose ( e ) {
  if ( !e.keyCode || e.keyCode === 27 ) {
    // code to close modal goes here
  }
}

document.addEventListener('keydown', modalClose);

Moreover, closing a full-screen modal when the overlay is clicked is conventional. The exception is if you don’t want to close the modal until the user has performed an action.

Use the following JavaScript to close the modal when the user clicks on the overlay:

mOverlay.addEventListener('click', function( e )
  if (e.target == modal.parentNode)
    modalClose( e );
  }
}, false);

Additional Accessibility Steps Link

Beyond the usability steps covered above, ARIA roles, states and properties3 will add yet more hooks for assistive technologies. For some of these, nothing more is required than adding the corresponding attribute to your markup; for others, additional JavaScript is required to control an element’s state.

aria-hidden Link

Use the aria-hidden attribute. By toggling the value true and false, the element and any of its children will be either hidden or visible to screen readers. However, as with all ARIA attributes, it carries no default style and, thus, will not be hidden from sighted users. To hide it, add the following CSS:

.modal-window[aria-hidden=”true”] {
  display: none;
}

Notice that the selector is pretty specific here. The reason is that we might not want all elements with aria-hidden="true" to be hidden (as with our earlier example of the “X” for the “close” button).

role=”dialog” Link

Add role="dialog" to the element that contains the modal’s content. This tells assistive technologies that the content requires the user’s response or confirmation. Again, couple this with the JavaScript that shifts focus from the last active element in the document to the modal or to the first focusable element in the modal.

However, if the modal is more of an error or alert message that requires the user to input something before proceeding, then use role="alertdialog" instead. Again, set the focus on it automatically with JavaScript, and confine focus to the modal until action is taken.

aria-label Link

Use the aria-label or aria-labelledby attribute along with role="dialog". If your modal window has a heading, you can use the aria-labelledby attribute to point to it by referencing the heading’s ID. If your modal doesn’t have a heading for some reason, then you can at least use the aria-label to provide a concise label about the element that screen readers can parse.

What About HTML5’s Dialog Element? Link

Chrome 37 beta and Firefox Nightly 34.0a1 support the dialog element, which provides extra semantic and accessibility information for modal windows. Once this native dialog element is established, we won’t need to apply role="dialog" to non-dialog elements. For now, even if you’re using a polyfill for the dialog element, also use role="dialog" so that screen readers know how to handle the element.

The exciting thing about this element is not only that it serves the semantic function of a dialog, but that it come with its own methods, which will replace the JavaScript and CSS that we currently need to write.

For instance, to display or dismiss a dialog, we’d write this base of JavaScript:

var modal = document.getElementById('myModal'),
  openModal = document.getElementById('btnOpen'),
  closeModal = document.getElementById('btnClose');

// to show our modal
openModal.addEventListener( 'click', function( e ) {
  modal.show();
  // or
  modal.showModal();
});

// to close our modal
closeModal.addEventListener( 'click', function( e ) {
  modal.close();
});

The show() method launches the dialog, while still allowing users to interact with other elements on the page. The showModal() method launches the dialog and prevents users from interacting with anything but the modal while it’s open.

The dialog element also has the open property, set to true or false, which replaces aria-hidden. And it has its own ::backdrop pseudo-element, which enables us to style the modal when it is opened with the showModal() method.

There’s more to learn about the dialog element than what’s mentioned here. It might not be ready for prime time, but once it is, this semantic element will go a long way to helping us develop usable, accessible experiences.

Where To Go From Here? Link

Whether you use a jQuery plugin or a homegrown solution, step back and evaluate your modal’s overall usability and accessibility. As minor as modals are to the web overall, they are common enough that if we all tried to make them friendlier and more accessible, we’d make the web a better place.

I’ve prepared a demo of a modal window4 that implements all of the accessibility features covered in this article.

(hp, il, al, ml)

Footnotes Link

  1. 1 https://www.smashingmagazine.com/2009/05/27/modal-windows-in-modern-web-design/
  2. 2 https://www.smashingmagazine.com/wp-content/uploads/2014/inaccessible.html
  3. 3 http://www.w3.org/TR/wai-aria/
  4. 4 https://www.smashingmagazine.com/wp-content/uploads/2014/accessible.html
SmashingConf New York

Hold on, Tiger! Thank you for reading the article. Did you know that we also publish printed books and run friendly conferences – crafted for pros like you? Like SmashingConf Barcelona, on October 25–26, with smart design patterns and front-end techniques.

↑ Back to top Tweet itShare on Facebook

Scott O'Hara is a UX designer & developer based out of Boston Massachusetts. He loves pushing the limits of CSS, designing usable experiences for everyone, writing about what he knows & what he's learning.

  1. 1

    Great to see an article like this put together. The addition of lastFocus was a particularly nice touch; it’s so often overlooked!

    11
  2. 2

    An excellent article that covers a lot of important details. Thanks for sharing it!

    Modals are hare hard to implement correctly, especially across the multiple viewports sizes we have available today. and ensuring that they are accessible is paramount in the implementation.

    I’ve tried my best to ensure that the modal implementation in my responsive framework covers all required aspects plus threw in a few bells and whistles also. http://responsivebp.com/javascript/modal.html

    I’d love feedback if you have a moment. :)

    0
    • 3

      At first glance this looks pretty cool! One error i caught looking at it — the aria-role on the modal should be “dialog”, not “document”.

      2
      • 4

        Hi Adrienne,

        I do have a dialog role on the main modal container, and the document role was added due to some screen readers (NVDA) having issues with virtual cursors within an element with a dialog role.

        by having the container with the dialog role and the content of the dialog with the document role, this circumvents that issue. If it weren’t for that corner case though, you’d be right, and the document role wouldn’t be needed.

        Thanks

        0
  3. 5

    Andrew Hamilton

    September 15, 2014 2:01 pm

    Great article, but your final demo close button has no hover state, or is broken on Chrome Linux, so it feels a bit off.

    0
    • 6

      You’re right Andrew.

      Seems I accidentally deleted that rule. I fixed it on the codepen that’s linked to in the demo.

      Thanks

      0
  4. 7

    What is the function of the |s| operator? Bitwise or?

    0
  5. 9

    Love this article – thank you for getting the word out about accessible modals.

    One other issue with modals that I’ve faced is in scrolling – so often lightboxes are positioned fixed to the center of the page so that the background page scrolls but the lightbox stays in center. This is great ONLY if the lightbox always fits in the viewport… but if the modal is too tall, watch out – you can never reach the bottom. :( It takes tricky logic to keep the centering effect (on window size change, etc) but allow the modal to scroll if necessary.

    But I digress – the main problem I’ve seen with modals is their lack of keyboard and screenreader a11y and you’ve done a great job detailing how to fix that. Thank you!

    0
  6. 10

    Well done.

    -1
  7. 11

    Glad to see articles like this.

    I have a question though, about the codepen demo – function focusRestrict is creating event listener on document for focus on each keypress on window. Is it intended or am I missing something?

    0
    • 12

      oh, yeah. umm. you’re right to be pointing that out. my bad.

      So yeah, I really shouldn’t have been double calling that eventListener.

      Instead I’ve changed it to:

      function focusRestrict ( event ) {
      if ( modalOpen && !modal.contains( event.target ) ) {
      event.stopPropagation();
      modal.focus();
      }
      }

      and

      for (i = 0; i < allNodes.length; i++) {
      allNodes.item(i).addEventListener('focus', focusRestrict);
      }

      Basically, now instead of looking for keypress in the window, I'm looking for focusing in any element on the page if the modal window is open.

      If it is, and an element outside of the modal is going to be focused, focusRestrict will run and kick the focus back to the modal window.

      Thank you for pointing that out.

      -2
      • 13

        That amend doesn’t work (at least not in Firefox on Mac).

        It breaks all the good work of restricting tab order to modal, to allowing the background page elements to be tabbed too … consequently causing the default modal usability problem.

        1
        • 14

          Dave Collins

          July 24, 2015 2:18 pm

          In researching accessible modals, I’ve landed here. Great stuff! Taking lots of notes.

          I think the poster here is noticing the same thing I am, which is that, in the demo, the tab order includes the background document. Isn;t that what we’re trying to prevent?

          0
  8. 16

    I am increasingly going off Modal along with carousels as they tend not to lend themselves well to small viewports

    3
    • 17

      I agree that some logic is required to determine when a modal is appropriate. That said, some native applications on small devices can also return warning and notice windows that don’t fit on the screen. I have seen this on the iphone a few times and the default support was to have no access to the cut off text (no scrolling).

      It’d be nice if native OS could handle this better, but as this is not always true from my experience, then maybe a quick check if whether the modal would fit in the document window should suffice. I am considering this myself as I also came to this conclusion.

      0
  9. 18

    The document collaboration DBook.org uses tag since it’s beta launch and works well all older browsers too (FF 24, IE 9, older Chrome version).

    Sure that “Chrome 37 beta and Firefox Nightly 34.0a1 ” that is true.

    -1
  10. 19

    Johan Van den Rym

    September 16, 2014 3:38 pm

    You could also add a description of the modal ‘what data it collects or what it conveys’.

    You can change your details at any time in the user account section.
    https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_dialog_role

    -1
  11. 20

    About the correct solution, when I click the modal button and the modal shows, the button remains on top of the modal until I move the mouse out of it :) Great article!

    -2
  12. 21

    Great article. Found a problem in the demo when using JAWS and IE11, however. “Invalid entry” is spoken when an email address is entered. Also, I get a perpetual “loading…” message in Codepen when I try to review the code. Any idea why?

    0
    • 22

      That’s not really an issue with the modal window though, as much as it’s an issue with the input (not part of the article).

      Also, why would he know about problems on codepen?

      0
  13. 23

    The < dialog > element is the way to go. Do you have any good polyfill to recommend ?

    0
  14. 24

    In the demo, the focus doesn’t shift to the first element of the dialog. And the element I was focused on doesn’t get focus when I close the dialog.
    (I am on Chrome v 37.0.2062.120 m)

    You did mention these two things in your post.

    0
    • 25

      Hi Kris,

      We tested this quite a bit before publishing, but I just re-checked in Chrome, and it does focus is set to the container modal window, and focus gets reset back to the open button on close for me. Checked it out on both mac/pc and in firefox on mac and pc as well.

      Dunno why you’re having this issue. Sorry.

      0
      • 26

        I can confirm that the focus is not always restricted to the modal. On first page load, if you click on the “Open the Modal” button, you can tab through all the elements of the page under the modal. Also on Chrome 37.0.2062.122

        0
  15. 27

    Great article!

    One question to the code:
    What’s “|s|” in “if ( !e.keyCode |s| e.keyCode === 27 ) {” doing?

    Thanks,
    Albert

    0
  16. 28

    What an excellent, well-written article! I was thinking throughout it, why can’t we just add aria-hidden to the non-dialog content, assuming that is housed in a container element? Wouldn’t that remove the need to prevent focusing all other elements in the rest of the dom? That way, the modal could be a sibling of the container element, and it could be immediately after the body, as you suggest.

    0
  17. 29

    One other thing I think was left out has to do with affordance: when creating a modal, make sure to clearly label the link with either an icon or image (ideally supplemented with aria-labelled-by attributes). That way, users understand what’s going to happen when they click the button/link. For example:

    “Subscribe now [icon]”

    Here’s a current example of a bad modal: http://www.helpscout.net/blog/user-onboarding-mistakes/ – the irony really gets me, given the title of that page. Try scrolling down.

    There’s nothing worse than expecting a new page, and you get a modal, squeezed up on your mobile device, and have to scroll up and zoom out to see it.

    0
  18. 30

    Also nice to disable mouse scroll wheel on the overlay if you can. If you have a scrollable region within your modal and the user uses their mouse-wheel to scroll that content (which they probably will) when they hit the bottom of that region, the scroll is passed on to the window ‘below’ causing the whole page to scroll so you can see the content scrolling underneath the semi-transparent grey overlay.

    1
  19. 31

    Here a walkthrough on how to create a modal window that blurs everything what underlays it without any JavaScript. It also shows a fallback code for the browsers not supporting :target pseudo-selector. http://dsheiko.com/weblog/fancy-modal-windows-without-javascript/
    Codepen links included

    -1
    • 32

      Those examples work similar to default modal behaviour; the background elements are still accessible. This article is about ‘better modals’ rather than just modals. Bluring is nice option though.

      0
  20. 33

    You dont need Javascript to create modals, you can do it with just CSS
    http://codepen.io/nodws/pen/dvusI

    0
    • 34

      You’re right. you don’t ‘need’ javascript to make a modal window.

      but a css only solution isn’t an accessible solution. hence the article :)

      0

↑ Back to top