Modal Dialog [ARIA Accessible] (using CSS, JS)

by Konstantin Rouda

HTML

<!--Dialog may be defined above/below the main content of the page (e.g. above/below MAIN/FOOTER element)-->
<!--if no suitable heading or labelling element exists, we can specify the label in an aria-label property instead-->
<section class="o-dialog" id="dialog" hidden inert role="dialog" aria-labelledby="dialog-title" aria-hidden="true">

  <!--To assist older screen reader versions (NVDA in particular), any immediate child of the dialog role must have a document role (if it contains content). In time, as screen reader support improves, the requirement for this role may go away.-->
  <section class="o-dialog__window" role="document">
  
    <header class="o-dialog__header">
      <h2 class="o-dialog__title" id="dialog-title">Dialog Title</h2>
      <button type="button" id="modal-close-btn" aria-label="close dialog" class="o-dialog__close-btn">
        <i class="o-dialog__close-icon" aria-hidden="true"></i>
      </button>
    </header>
    
    <article class="o-dialog__body">
      
      <p>
      Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. 
      </p>
      <input type="text" />
      <a href="#x" class="o-dialog__link">www.mystie.com</a>
      
    </article>
    
  </section>
  
  <!--
      NOTE: 
      1. Because the mask does not contain content, it does not need a document role.
      2. The overlay does not prevent keyboard or screen reader 
      from accessing the page content behind the window (we need JavaScript for that)
  -->
  <div class="o-dialog__overlay" id="dialog-overlay"></div>
  
</section>



<!--Other content-->
<main>
  
  <section class="o-tile">
  
    <header...

CSS

HTML {
  /*using system font-stack*/
  /*font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;*/
  font-family: Raleway, sans-serif;
  font-size: 115%; /*~18px*/
  line-height: 1.5;
  box-sizing: border-box;
}

BODY {
  margin: 0;
  color: #3a3d40;
  background: rgb(252, 252, 252);
}

*, *::before, *::after {
  box-sizing: inherit;
  color: inherit;
}

IMG { max-width: 100%; }

MAIN, SECTION, ARTICLE { display: block; }

[hidden] { /*for browsers that don't support it (e.g. IE <= 10)*/
  display: none;
}


/*Actual Style*/

/*=======================
       O-Dialog
=========================*/
.o-dialog {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: .4rem;
  z-index: 999;
}


/*=======================
     O-Dialog Window
=========================*/
.o-dialog__window {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 980px; 
  // height: 100%;
  min-width: 270px;
  margin: 0 auto;
  transform: scale(.5);
  opacity: 0;
  transition: transform .3s ease, opacity .35s .1s ease;
  z-index: 1; /*in order to be above the overlay*/
}


.o-dialog.is-open .o-dialog__window  {
  transform: scale(1);
  opacity: 1;
}


/*=======================
     O-Dialog Header
=========================*/
.o-dialog__header {
  display: flex;
  flex-shrink: 0;
  padding: .4rem;
  border-bottom: 1px solid rgb(204, 204, 204); 
  background-color: rgb(250, 250, 250);
}

.o-dialog__title {
  width: 100%;
  margin: 0; padding: 0;
  align-self: center;
  text-align: center;
}

.o-dialog__close-btn {
  padding: 0 1.2rem;
  border: 1px solid rgb(204, 204, 204);
  border-radius: .17rem;
  background: rgb(238, 238, 238);
}

.o-dialog__close-icon::before,
.o-dialog__close-icon::after {
  content: "";
  display: block;
  width: .12rem; height: 1.2rem;
 ...

JavaScript

/*inert polyfill [ https://github.com/WICG/inert ]*/
;(function() {
  "use strict";
  
  (function(global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
        typeof define === 'function' && define.amd ? define(factory) :
        (factory());
}(this, (function() {
    'use strict';

    /**
     * Determine if a DOM element matches a CSS selector
     *
     * @param {Element} elem
     * @param {String} selector
     * @return {Boolean}
     * @api public
     */

    function matches(elem, selector) {
        // Vendor-specific implementations of `Element.prototype.matches()`.
        var proto = window.Element.prototype;
        var nativeMatches = proto.matches ||
            proto.mozMatchesSelector ||
            proto.msMatchesSelector ||
            proto.oMatchesSelector ||
            proto.webkitMatchesSelector;

        if (!elem || elem.nodeType !== 1) {
            return false;
        }

        var parentElem = elem.parentNode;

        // use native 'matches'
        if (nativeMatches) {
            return nativeMatches.call(elem, selector);
        }

        // native support for `matches` is missing and a fallback is required
        var nodes = parentElem.querySelectorAll(selector);
        var len = nodes.length;

        for (var i = 0; i < len; i++) {
            if (nodes[i] === elem) {
                return true;
            }
        }

        return false;
    }

    /**
     * Expose `matches`
     */

    var index = matches;

    /**
     * Module exports.
     */

    var index$1 = contains;

    /**
     * `Node#contains()` polyfill.
     *
     * See: http://compatibility.shwups-cms.ch/en/polyfills/?&id=1
     *
     * @param {Node} node
     * @param {Node} other
     * @return {Boolean}
     * @public
     */

    function contains(node, other) {
        return node === other || !!(node.compareDocumentPosition(other) & 16);
    }

    var classCallCheck = function(instance,...