<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?"
}
]
}
.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);
}
}
}
}
/* 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
}
})()
}
!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() } } })() });
MicroModal.init({
disableScroll: true,
});
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.
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.modal__container
should include a role="dialog"
attribute.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.aria-label
attribute to define the intention of the relative button.data-micromodal-close
attribute to trigger the close event.Enter
or Space
= Open/close modalEsc
= Close modalTab
= Move to next focusable element