<header>
  <h1>Accessible Modals</h1>
</header>

<main>
  <p>A modal (or dialog) is a box that pops open on top of the webpage, usually covering the whole page or a major portion of the page.</p>
  
  <h2>What makes an accessible modal?</h2>
  
  <p>In order to be accessible, modals:</p>
  
  <ol>
    <li>should be hidden with <code>visibility: hidden</code>,</li>
    <li>should have an accessible name,</li>
    <li>should have a <code>role="dialog"</code> assigned,</li>
    <li>should move focus inside the dialog,</li>
    <li>should not allow focus outside of the box,</li>
    <li>can be closed with <kbd>Esc</kbd>,</li>
    <li>can be closed with a touch/click outside of box,</li>
    <li>can be closed with a close button (if present),</li>
    <li>when closed, should return user focus to the element that initiated the modal,</li>
  </ol>
  
  <button id="open" type="button">Open modal</button>
  
  <dialog role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-desc">
    <h2 id="dialog-title" tabindex="-1">Dialog Title</h2>
    
    <p id="dialog-desc">This is a dialog that just contains extra text. Dialogs or modals can also hold form elements.</p>
    
    <button id="close" type="button">OK</button>
  </dialog>
  
  <h2>Why create a custom modal?</h2>
  
  <p>The <code>dialog</code> element is not supported across most browsers.</p>
  
  <p class="ciu_embed embed" data-feature="dialog" data-periods="future_1,current,past_1,past_2" data-accessible-colours="false">
    </p>
  
  <h2>People who know this stuff</h2>
  
  <ul>
    <li>Scott O'Hara: <a href="https://scottaohara.github.io/accessible_modal_window/">Accessible Modal Dialogs with Vanilla JS</a></li>
    <li>W3C: <a href="https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/dialog.html">Modal Dialog Example</a></li>
    <li>Harvard University: <a href="">Support Keyboard Interaction</a></li>
  </ul>
</main>

<footer>
  An experiment for <a href="https://100daysofa11y.com">100 Days for A11y</a>
</footer>
body {
  line-height: 1.5;
}

header,
main {
  max-width: 720px;
  margin: .5em auto;
  padding: 0 .8em;
}

header {
  border-bottom: 1px solid #333;
}

footer {
  background-color: #333;
  color: #f7f7f7;
  margin: 3em 0 0;
  padding: .8em .5em;
  border-top: 1px solid #333;
}
footer a {
  color: gold;
}

[role="dialog"] {
  visibility: hidden;
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  background-color: #f7f7f7;
  color: #222;
  width: 100%;
  height: 100%;
  border: 2px double #555;
}
const modal = document.querySelector('[role="dialog"]'),
      btnOpen = document.getElementById('open'),
      btnClose = document.getElementById('close'),
      dialogTitle = document.getElementById('dialog-title');

function openModal() {
  modal.style.visibility = 'visible';
  dialogTitle.focus();
  document.addEventListener('keydown', useEsc);
}

function closeModal() {
  modal.style.visibility = 'hidden';
  document.removeEventListener('keydown', useEsc);
  btnOpen.focus();
}

function useEsc(e) {
  if (e.keyCode == 27) {
    closeModal();
  }
}

function preventTab(e) {
  if(e.keyCode == 9) {
    dialogTitle.focus();
    e.preventDefault();
  }
}

btnOpen.addEventListener('click', openModal);
btnClose.addEventListener('keydown', preventTab);
btnClose.addEventListener('click', closeModal);
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.jsdelivr.net/gh/ireade/caniuse-embed/caniuse-embed.min.js