Usage
<script src="/src/js/modal-init.js"></script>
<dialog class="modal">
<form method="dialog">
<button class="button theme">Close</button>
</form>
<div class="content">
<div class="l-stack">
<h2>Some Content</h2>
<p>Some text</p>
<a href="/">Call to action</a>
</div>
</div>
</dialog>
<button class="button theme showModal" style="margin-inline: auto;">Open Modal</button>
.modal {
top: 50%;
left: 50%;
translate: -50% 50%;
&, &::backdrop {
opacity: 0;
}
&::backdrop {
background: black;
}
&[open] {
opacity: 1;
translate: -50% -50%;
transition:
display 300ms linear allow-discrete,
overlay 300ms linear allow-discrete,
translate 250ms var(--ease-out-3) allow-discrete,
opacity 300ms linear;
&::backdrop {
opacity: 0.5;
}
}
@starting-style {
&[open],
&[open] {
opacity: 0;
translate: -50% calc(-50% + 20px);
}
}
}
import {
disableBodyScroll,
enableBodyScroll
} from "body-scroll-lock";
import {queryFocuseable, trapFocus} from "./utilities/accessibility";
export default class Modal {
constructor(dialog, openBtn) {
this.dialog = dialog;
this.openBtn = openBtn;
this.showModalBound = this.showModal.bind(this);
this.openBtn.addEventListener("click", this.showModalBound);
this.dialog.addEventListener("close", this.closeModal.bind(this));
document.addEventListener('keydown', this.closeOnEscapePress.bind(this));
}
showModal() {
this.dialog.showModal();
disableBodyScroll(this.dialog, { reserveScrollBarGap: true });
this.focuseable = queryFocuseable(this.dialog);
this.focuseable.firstFocuseable.focus();
this.boundTrapFocusHandler = this.trapFocusHandler.bind(this);
document.addEventListener("keydown", this.boundTrapFocusHandler);
}
closeModal() {
enableBodyScroll(this.dialog);
document.removeEventListener('keydown', this.boundTrapFocusHandler);
}
trapFocusHandler(event) {
trapFocus(event, this.focuseable);
}
closeOnEscapePress = (e) => {
if (e.key === "Escape" && this.dialog.hasAttribute('open')) {
this.dialog.close();
this.closeModal();
}
};
}
Todo
Document the approaches available of preventing scrolling when the modal is open. How to prevent layout shift when scrollbar disappears (more evident when the background content is visible)
The Dialog Element
This implimentation of the modal component uses the dialog element.
You can set the color of the background behind the modal using the ::backdrop
psuedo-selector. However, it does not currently inherit custom properties.
Scrolling
User scroll should be prevented when the modal is open. This implimentation uses the body-scroll-lock package, which handles scroll locking for a variety of browsers and device types.
Keyboard Navigation
This implimentation provides focus trapping to keep the tabbing context within the modal.
Future Additions
- document the styles that are included (ex. positioning)