another aria Modal - focus trap - last focus

Modal

by ian_smithz

HTML

<div id="page">

      <div class="menu">
        <button class="btn" type="button" id="modal_open">Open the Modal</button>
      </div>

      <article class="content-area">

        <h1>
          Demo:<br />
          An accessible Modal Window with JavaScript &amp; CSS
        </h1>
        
        <p>
          <b>Note</b> - There's a new version of this script that you can find on my <a href="https://github.com/scottaohara/accessible_modal_window">github page</a>.
        </p>

        <p>
          This modal window is made with plain old semantic mark-up, CSS and
          a very little bit of JavaScript.
        </p>

        <p>
          Check out the <a href="https://codepen.io/scottohara/pen/lIdfv">source code on codepen</a> to see how it works!
        </p>

        <form action="#">
          <label for="test" class="label">Label for Input</label>
          <input type="text" id="test" class="input" />

          <label for="test2" class="label">Label for Second Input</label>
          <input type="text" id="test2" class="input" />

          <label for="test3" class="label">Label for Third Input</label>
          <input type="text" id="test3" class="input" />
        </form>

        <p>
          Here's a link to the
          <a href="https://en.wikipedia.org/wiki/Modal_window">wikipedia entry on modal windows</a>.
        </p>
      </article> <!-- end .content-area -->
    </div> <!-- end #page -->

    <div class="modal-overlay" id="modal_window"
             aria-hidden="true" role="dialog"
             aria-labelledby="modal_title">

      <div class="modal-content" id="modal_holder" role="document">

          <h1 id="modal_title">Modal Title</h1>
          <form>
            <label for="name">Name:</label>
            <input type="text" id="name" class="input" placeholder="Gimmie ur Name" />

            <label for="email">Email:</label>
            <input type="email" id="email" class="input" placeholder="Gimmie ur Email" />

          ...

CSS

/*
  Modal Overlay
*/
.modal-overlay {
  background:rgba(0,0,0,.8);
  height: 100%;
  left: 0;
  display: flex;
  overflow: auto;
  padding: 1.5em;
  position: fixed;
  top: 0;
  transition: opacity .2s;
  width: 100%;
  z-index: -1;
}

.modal-overlay[aria-hidden="true"] {
  opacity: 0;
  visibility: hidden;
}

.modal-overlay[aria-hidden="false"] {
  opacity: 1;
  visibility: visible;
  z-index: 2;
}

/*
  Modal Content Area
*/
.modal-content {
  background: #efefef;
  margin: auto;
  max-width: 800px;
  overflow: auto;
  overflow-x: hidden;
  padding: 1.5em;
  position: relative;
  transform: scale(.8);
  transition: transform .3s;
  width: 100%;
}

.modal-overlay[aria-hidden="false"] .modal-content {
  transform: scale(1);
}

/*
  General Modal Content
*/
#modal-title {
  margin-top: 0;
  padding-right: 1.25em;
}

/*
  Close Modal Button
*/
.btn-close {
  background: none;
  border: none;
  cursor: pointer;
  font-family: arial;
  font-size: 2em;
  font-weight: 800;
  line-height: 1;
  padding: 0;
  position: absolute;
  right: .25em;
  top: .25em;
  z-index: 2;
}

.btn-close:hover {
  color: #c12f48;
}

.btn-close:focus {
  outline: 1px dotted;
}


/*

  This is here purely for demo styling

*/

*, *:after, *:before {
  -webkit-box-sizing: border-box;
  -moz-box-sizing:border-box;
  box-sizing:border-box;
  font-family: arial;
}

body, html {
  background: #fafafa;
  color: #450c13;
  font-size: 20px;
  line-height: 1.3;
  margin: 0;
  padding: 0;
  -moz-text-size-adjust: 100%;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  text-rendering: optimizeLegibility;
}

a {
  color: #921c2b;
}

a:hover {
  color: #c12f48;
}

.content-area {
  margin: auto;
  max-width: 900px;
  padding: 20px;
}

.menu {
  background: #fff;
  border-bottom: 1px solid #bbb;
  margin: 0;
  padding: 12px 8px 0;
  text-align: center;
}

input[type="submit"],
input[type="button"] {
  -webkit-appearance:none;
}

button::-moz-focus-inner,
input::-moz-focus-inner {
  border:...

JavaScript

// helper function to place modal window as the first child
// of the #page node


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



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

swap();


// modal window
(function() {

  'use strict';

  // list out the vars
  var mOverlay = getId('modal_window'),
      mOpen = getId('modal_open'),
      mClose = getId('modal_close'),
      modal = getId('modal_holder'),
      allNodes = document.querySelectorAll("*"),
      allFocusableNodes = $('page').find(':focusable'),
      modalOpen = false,
      lastFocus,
      i;
      
console.log(allNodes);
console.log(allFocusableNodes);

  // Let's cut down on what we need to type to get an ID
  function getId ( id ) {
    return document.getElementById(id);
  }


  // Let's open the modal
  function modalShow () {
    lastFocus = document.activeElement;
    mOverlay.setAttribute('aria-hidden', 'false');
    modalOpen = true;
    modal.setAttribute('tabindex', '0');
    modal.focus();
  }


  // binds to both the button click and the escape key to close the modal window
  // but only if modalOpen is set to true
  function modalClose ( event ) {
    if (modalOpen && ( !event.keyCode || event.keyCode === 27 ) ) {
      mOverlay.setAttribute('aria-hidden', 'true');
      modal.setAttribute('tabindex', '-1');
      modalOpen = false;
      lastFocus.focus();
    }
  }


  // Restrict focus to the modal window when it's open.
  // Tabbing will just loop through the whole modal.
  // Shift + Tab will allow backup to the top of the modal,
  // and then stop.
  function focusRestrict ( event ) {
    if ( modalOpen && !modal.contains( event.target ) ) {
      event.stopPropagation();
      modal.focus();
    }
  }


  // Close modal window by clicking on the overlay
  /*
  mOverlay.addEventListener('click', function( e ) {
    if (e.target == modal.parentNode) {
       modalClose( e );
     }
  }, false);
*/

  // open modal by btn...