Modal

<button data-micromodal-trigger="modal-1">
    Open Modal
  </button>

<!-- Modal -->
<div class="modal" id="modal-1" aria-hidden="true">
    <div class="modal__overlay" tabindex="-1" data-micromodal-close>
        <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1">
            <button class="modal__close" aria-label="Close this dialog window" data-micromodal-close>
          <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 18 18" class="modal__close-svg" role="presentation" aria-hidden="true">
            <path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"/>
          </svg>
        </button>
            <div class="modal__content">
                <p class="modal__text-body">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.</p>
            </div>
        </div>
    </div>
</div>
<button data-micromodal-trigger="modal-2">
    Open Modal with scrolling copy
  </button>

<!-- Modal -->
<div class="modal" id="modal-2" aria-hidden="true">
    <div class="modal__overlay" tabindex="-1" data-micromodal-close>
        <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-2">
            <button class="modal__close" aria-label="Close this dialog window" data-micromodal-close>
          <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 18 18" class="modal__close-svg" role="presentation" aria-hidden="true">
            <path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"/>
          </svg>
        </button>
            <div class="modal__content">
                <p class="modal__text-body">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.Nemo enim ipsam voluptatem
                    quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non
                    numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem
                    vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
            </div>
        </div>
    </div>
</div>
{% for modal in modals %}
  <button data-micromodal-trigger="{{ modal.id }}">
    {{ modal.buttonLabel }}
  </button>

  <!-- Modal -->
  <div class="modal" id="{{ modal.id }}" aria-hidden="true">
    <div class="modal__overlay" tabindex="-1" data-micromodal-close>
      <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="{{ modal.id }}">
        <button class="modal__close" aria-label="Close this dialog window" data-micromodal-close>
          <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 18 18" class="modal__close-svg" role="presentation" aria-hidden="true">
            <path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"/>
          </svg>
        </button>
        <div class="modal__content">
          {% if modal.header %}
            <h2>{{ modal.header }}</h2>
          {% endif %}
          {% if modal.body %}
            <p class="modal__text-body">{{ modal.body }}</p>
          {% endif %}
        </div>
      </div>
    </div>
  </div>
{% endfor %}
{
  "modals": [
    {
      "id": "modal-1",
      "buttonLabel": "Open Modal",
      "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo."
    },
    {
      "id": "modal-2",
      "buttonLabel": "Open Modal with scrolling copy",
      "body": "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?"
    }
  ]
}
  • Content:
    .modal {
      display: none;
    
      &.is-open {
        display: block;
      }
    
      &__overlay {
        z-index: z-index(overlay);
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        display: flex;
        align-items: center;
        justify-content: center;
        background: rgba(0,0,0, .6);
      }
    
      &__container {
        position: relative;
        padding: 30px;
        max-width: 500px;
        max-height: 100vh;
        overflow-y: auto;
        background-color: #fff;
      }
    
      &__text-body {
        max-height: 350px;
        overflow-y: auto;
      }
    
      &__close {
        position: absolute;
        top: 5px;
        right: 5px;
        border: 0;
        padding: 0;
        background-color: transparent;
        cursor: pointer;
    
        svg {
          width: 18px;
          height: 18px;
          pointer-events: none;
          transition: fill $transition-duration $transition-easing;
        }
    
        &:hover,
        &:focus {
    
          svg {
            fill: color(primary, base);
          }
        }
      }
    }
    
  • URL: /components/raw/modal/_modal.scss
  • Filesystem Path: components/modal/_modal.scss
  • Size: 924 Bytes
  • Content:
    /* IE Polyfull */
    
    if (typeof Object.assign != "function") {
      Object.defineProperty(Object, "assign", {
        value: function assign(target, varArgs) {
          "use strict"
          if (target == null) {
            throw new TypeError("Cannot convert undefined or null to object")
          }
          var to = Object(target)
          for (var index = 1; index < arguments.length; index++) {
            var nextSource = arguments[index]
            if (nextSource != null) {
              for (var nextKey in nextSource) {
                if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                  to[nextKey] = nextSource[nextKey]
                }
              }
            }
          }
          return to
        },
        writable: true,
        configurable: true
      })
    }
    
    if (!Array.from) {
      Array.from = (function () {
        var toStr = Object.prototype.toString
        var isCallable = function (fn) {
          return typeof fn === "function" || toStr.call(fn) === "[object Function]"
        }
        var toInteger = function (value) {
          var number = Number(value)
          if (isNaN(number)) {
            return 0
          }
          if (number === 0 || !isFinite(number)) {
            return number
          }
          return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number))
        }
        var maxSafeInteger = Math.pow(2, 53) - 1
        var toLength = function (value) {
          var len = toInteger(value)
          return Math.min(Math.max(len, 0), maxSafeInteger)
        }
    
        return function from(arrayLike) {
          var C = this
          var items = Object(arrayLike)
          if (arrayLike == null) {
            throw new TypeError(
              "Array.from requires an array-like object - not null or undefined"
            )
          }
          var mapFn = arguments.length > 1 ? arguments[1] : void undefined
          var T
          if (typeof mapFn !== "undefined") {
            if (!isCallable(mapFn)) {
              throw new TypeError(
                "Array.from: when provided, the second argument must be a function"
              )
            }
            if (arguments.length > 2) {
              T = arguments[2]
            }
          }
          var len = toLength(items.length)
          var A = isCallable(C) ? Object(new C(len)) : new Array(len)
          var k = 0
          var kValue
          while (k < len) {
            kValue = items[k]
            if (mapFn) {
              A[k] =
                typeof T === "undefined"
                  ? mapFn(kValue, k)
                  : mapFn.call(T, kValue, k)
            } else {
              A[k] = kValue
            }
            k += 1
          }
          A.length = len
          return A
        }
      })()
    }
    
  • URL: /components/raw/modal/micromodal-polyfill.js
  • Filesystem Path: components/modal/micromodal-polyfill.js
  • Size: 2.5 KB
  • Content:
    !function (e, t) { "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = e || self).MicroModal = t() }(this, function () { "use strict"; return (() => { const e = ["a[href]", "area[href]", 'input:not([disabled]):not([type="hidden"]):not([aria-hidden])', "select:not([disabled]):not([aria-hidden])", "textarea:not([disabled]):not([aria-hidden])", "button:not([disabled]):not([aria-hidden])", "iframe", "object", "embed", "[contenteditable]", '[tabindex]:not([tabindex^="-"])']; class t { constructor({ targetModal: e, triggers: t = [], onShow: o = (() => { }), onClose: i = (() => { }), openTrigger: n = "data-micromodal-trigger", closeTrigger: s = "data-micromodal-close", disableScroll: a = !1, disableFocus: l = !1, awaitCloseAnimation: d = !1, awaitOpenAnimation: r = !1, debugMode: c = !1 }) { this.modal = document.getElementById(e), this.config = { debugMode: c, disableScroll: a, openTrigger: n, closeTrigger: s, onShow: o, onClose: i, awaitCloseAnimation: d, awaitOpenAnimation: r, disableFocus: l }, t.length > 0 && this.registerTriggers(...t), this.onClick = this.onClick.bind(this), this.onKeydown = this.onKeydown.bind(this) } registerTriggers(...e) { e.filter(Boolean).forEach(e => { e.addEventListener("click", e => this.showModal(e)) }) } showModal() { if (this.activeElement = document.activeElement, this.modal.setAttribute("aria-hidden", "false"), this.modal.classList.add("is-open"), this.scrollBehaviour("disable"), this.addEventListeners(), this.config.awaitOpenAnimation) { const e = () => { this.modal.removeEventListener("animationend", e, !1), this.setFocusToFirstNode() }; this.modal.addEventListener("animationend", e, !1) } else this.setFocusToFirstNode(); this.config.onShow(this.modal, this.activeElement) } closeModal() { const e = this.modal; this.modal.setAttribute("aria-hidden", "true"), this.removeEventListeners(), this.scrollBehaviour("enable"), this.activeElement && this.activeElement.focus(), this.config.onClose(this.modal), this.config.awaitCloseAnimation ? this.modal.addEventListener("animationend", function t() { e.classList.remove("is-open"), e.removeEventListener("animationend", t, !1) }, !1) : e.classList.remove("is-open") } closeModalById(e) { this.modal = document.getElementById(e), this.modal && this.closeModal() } scrollBehaviour(e) { if (!this.config.disableScroll) return; const t = document.querySelector("body"); switch (e) { case "enable": Object.assign(t.style, { overflow: "", height: "" }); break; case "disable": Object.assign(t.style, { overflow: "hidden", height: "100vh" }) } } addEventListeners() { this.modal.addEventListener("touchstart", this.onClick), this.modal.addEventListener("click", this.onClick), document.addEventListener("keydown", this.onKeydown) } removeEventListeners() { this.modal.removeEventListener("touchstart", this.onClick), this.modal.removeEventListener("click", this.onClick), document.removeEventListener("keydown", this.onKeydown) } onClick(e) { e.target.hasAttribute(this.config.closeTrigger) && (this.closeModal(), e.preventDefault()) } onKeydown(e) { 27 === e.keyCode && this.closeModal(e), 9 === e.keyCode && this.maintainFocus(e) } getFocusableNodes() { const t = this.modal.querySelectorAll(e); return Array(...t) } setFocusToFirstNode() { if (this.config.disableFocus) return; const e = this.getFocusableNodes(); e.length && e[0].focus() } maintainFocus(e) { const t = this.getFocusableNodes(); if (this.modal.contains(document.activeElement)) { const o = t.indexOf(document.activeElement); e.shiftKey && 0 === o && (t[t.length - 1].focus(), e.preventDefault()), e.shiftKey || o !== t.length - 1 || (t[0].focus(), e.preventDefault()) } else t[0].focus() } } let o = null; const i = e => { if (!document.getElementById(e)) return console.warn(`MicroModal: ❗Seems like you have missed %c'${e}'`, "background-color: #f8f9fa;color: #50596c;font-weight: bold;", "ID somewhere in your code. Refer example below to resolve it."), console.warn("%cExample:", "background-color: #f8f9fa;color: #50596c;font-weight: bold;", `<div class="modal" id="${e}"></div>`), !1 }, n = (e, t) => { if ((e => { if (e.length <= 0) console.warn("MicroModal: ❗Please specify at least one %c'micromodal-trigger'", "background-color: #f8f9fa;color: #50596c;font-weight: bold;", "data attribute."), console.warn("%cExample:", "background-color: #f8f9fa;color: #50596c;font-weight: bold;", '<a href="#" data-micromodal-trigger="my-modal"></a>') })(e), !t) return !0; for (var o in t) i(o); return !0 }; return { init: e => { const i = Object.assign({}, { openTrigger: "data-micromodal-trigger" }, e), s = [...document.querySelectorAll(`[${i.openTrigger}]`)], a = ((e, t) => { const o = []; return e.forEach(e => { const i = e.attributes[t].value; void 0 === o[i] && (o[i] = []), o[i].push(e) }), o })(s, i.openTrigger); if (!0 !== i.debugMode || !1 !== n(s, a)) for (var l in a) { let e = a[l]; i.targetModal = l, i.triggers = [...e], o = new t(i) } }, show: (e, n) => { const s = n || {}; s.targetModal = e, !0 === s.debugMode && !1 === i(e) || (o = new t(s)).showModal() }, close: e => { e ? o.closeModalById(e) : o.closeModal() } } })() });
  • URL: /components/raw/modal/micromodal.min.js
  • Filesystem Path: components/modal/micromodal.min.js
  • Size: 5.2 KB

Usage

An accessible modal treatment for displaying additional content in a floating window. This modal utilizes the Micromodal.js library.

This modal comes with a configuration attribute, data-micromodal-close, that should be added to the modal__overlay to allow users to close the modal when they click on the overlay.

Labelling Expectations

  • Each modal should have a unique id attribute.
  • If the modal is visible, the modal__container should have aria-hidden set to false. If the modal is not visible, aria-hidden is set to true. The Micromodal plugin takes care of these changes.
  • The modal__container should include a role="dialog" attribute.
  • The modal__container should include an aria-labelledby attribute. If the modal does not include a heading element, aria-labelledby should be set to the unique modal id.
  • All buttons should include an aria-label attribute to define the intention of the relative button.
  • Close buttons within a modal should include the data-micromodal-close attribute to trigger the close event.

Focus Expectations

  • Modal open/close buttons should have visible keyboard focus state

Keyboard Expectations

  • Enter or Space = Open/close modal
  • Esc = Close modal
  • Tab = Move to next focusable element