create a Modal component with plain vanilla JavaScript (Without React or Vue Framework), you can follow these steps:
Create an HTML file and include the following HTML structure for the modal:
<div class="vanilla-modal">
<div class="modal">
<div class="modal-inner">
<div id="modal-content"></div>
</div>
</div>
</div>
Create a CSS file and add the provided styles for the modal.
.modal{
display: block;
position: fixed;
content: "";
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: -1;
opacity: 0;
transition: opacity 0.2s, z-index 0s 0.2s;
text-align: center;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.modal > * {
display: inline-block;
white-space: normal;
vertical-align: middle;
text-align: left;
}
.modal:before {
display: inline-block;
overflow: hidden;
width: 0;
height: 100%;
vertical-align: middle;
content: "";
}
.modal-visible .modal {
z-index: 9999;
opacity: 1;
transition: opacity 0.2s;
}
.modal-inner {
position: relative;
overflow: hidden;
max-width: 90%;
max-height: 90%;
background: #fff;
z-index: -1;
opacity: 0;
transform: scale(0);
transition: opacity 0.2s, transform 0.2s, z-index 0s 0.2s;
min-width: 500px;
border-radius: 6px;
}
.modal-visible .modal-inner {
z-index: 100;
opacity: 1;
transform: scale(1);
transition: opacity 0.2s, transform 0.2s;
}
#modal-content{
padding: 50px 70px;
}
Create Vanilla Plain JavaScript Component
This code allows you to initialize the modal, open it, and close it. The modal will listen for clicks outside the modal, clicks on the close button, pressing the Escape key, and handling tab navigation inside the modal.
function outsideClick(e) {
if (e.target.closest(".modal-inner")) {
return;
}
const modalVisible = document.querySelector(".modal-visible");
if (modalVisible) {
closeModal();
}
}
function escKey(e) {
if (e.keyCode == 27) {
closeModal();
}
}
function closeClick(e) {
if (e.target.classList.contains("closeModal")) {
closeModal();
}
}
function trapTabKey(e) {
const vanillaModal = document.querySelector(".vanilla-modal");
const FOCUSABLE_ELEMENTS = [
"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^="-"])',
];
const nodes = vanillaModal.querySelectorAll(FOCUSABLE_ELEMENTS);
let focusableNodes = Array(...nodes);
if (focusableNodes.length === 0) return;
focusableNodes = focusableNodes.filter((node) => {
return node.offsetParent !== null;
});
// if disableFocus is true
if (!vanillaModal.contains(document.activeElement)) {
focusableNodes[0].focus();
} else {
const focusedItemIndex = focusableNodes.indexOf(document.activeElement);
if (e.shiftKey && focusedItemIndex === 0) {
focusableNodes[focusableNodes.length - 1].focus();
e.preventDefault();
}
if (
!e.shiftKey &&
focusableNodes.length > 0 &&
focusedItemIndex === focusableNodes.length - 1
) {
focusableNodes[0].focus();
e.preventDefault();
}
}
}
function closeModal() {
const vanillaModal = document.querySelector(".vanilla-modal");
if (vanillaModal) {
vanillaModal.classList.remove("modal-visible");
document.getElementById("modal-content").innerHTML = "";
document.getElementById("modal-content").style = "";
}
document.removeEventListener("keydown", escKey);
document.removeEventListener("click", outsideClick, true);
document.removeEventListener("click", closeClick);
document.removeEventListener("keydown", trapTabKey);
}
const modal = {
init: function () {
const prerendredModal = document.createElement("div");
prerendredModal.classList.add("vanilla-modal");
const htmlModal = `
<div class="modal">
<div class="modal-inner"
><div id="modal-content"></div></div></div>`;
prerendredModal.innerHTML = htmlModal;
document.body.appendChild(prerendredModal);
},
open: function (idContent, option = { default: null }) {
let vanillaModal = document.querySelector(".vanilla-modal");
if (!vanillaModal) {
console.log("there is no vanilla modal class");
modal.init();
vanillaModal = document.querySelector(".vanilla-modal");
}
const content = document.getElementById(idContent);
let currentModalContent = content.cloneNode(true);
currentModalContent.classList.add("current-modal");
currentModalContent.style = "";
document.getElementById("modal-content").appendChild(currentModalContent);
if (!option.default) {
if (option.width && option.height) {
document.getElementById("modal-content").style.width = option.width;
document.getElementById("modal-content").style.height = option.height;
}
}
vanillaModal.classList.add("modal-visible");
document.addEventListener("click", outsideClick, true);
document.addEventListener("keydown", escKey);
document.addEventListener("keydown", trapTabKey);
document
.getElementById("modal-content")
.addEventListener("click", closeClick);
},
close: function () {
closeModal();
},
};
// for webpack es6 use uncomment the next line
// export default modal;
The
outsideClick
function is responsible for handling clicks outside the modal. If the click target is not inside the modal, it triggers thecloseModal
function to close the modal.The
escKey
function checks if the Escape key was pressed (keyCode 27), and if so, it calls thecloseModal
function.The
closeClick
function listens for a click event on the close button within the modal. If the click target has the class "closeModal", it triggers thecloseModal
function.The
trapTabKey
function is used to trap keyboard focus within the modal. It first retrieves all the focusable elements inside the modal based on the provided arrayFOCUSABLE_ELEMENTS
. Then it filters out any elements that are not currently visible.
If there are no focusable elements, it simply returns.
If the modal does not currently contain focus (meaning no element inside the modal is currently focused), it sets the focus on the first focusable element.
-
If the modal currently contains focus, it handles the tab navigation logic:
- If the Shift key is pressed along with Tab, it moves focus to the last focusable element and prevents the default tab behavior.
- If only the Tab key is pressed, and the currently focused element is the last focusable element, it moves focus back to the first element and prevents the default tab behavior.
The
closeModal
function is responsible for closing the modal. It finds the modal element and removes the classes and content related to the visibility of the modal. It also removes the event listeners for keydown, clicks outside the modal, and clicks on the close button.-
The
modal
object has three methods:-
init()
: This method is responsible for initializing the modal. It creates the HTML structure of the modal and appends it to the document body. -
open(idContent, option)
: This method is used to open the modal. It takes two parameters:-
idContent
(required): The ID of the element that contains the content to be shown in the modal. -
option
(optional): An object that allows specifying additional options. Currently, it supports setting the width and height of the modal by providing thewidth
andheight
properties in theoption
object.
-
-
close()
: This method simply calls thecloseModal
function to close the modal.
-
To use the modal
object, you can call the open
method with the ID of the content you want to show in the modal. For example:
modal.open("your-modal-content-id");
You can also provide optional options to adjust the width and height of the modal:
modal.open("your-modal-content-id", { width: "500px", height: "300px" });
To close the modal, simply call the close
method:
modal.close();
With these steps in place, you can now use the modal object to open and close the modal:
// Opening the modal
modal.open("your-modal-content-id");
// Closing the modal
modal.close();
Make sure to replace "your-modal-content-id" with the actual ID of your modal content.
You can modify the provided code as per your requirement or style the modal further based on your design preferences.
Github Repository Link :
https://github.com/idurar/vanilla-js-modal
And don't forget to star our Open Source ERP / CRM π€© repo in github π ? !
Github Repo : https://github.com/idurar/erp-crm
Idurar is a modern and open-source ERP/CRM system based on Node.js React.js that offers features such as sales management, customer management, and invoicing.
Top comments (4)
...or just use the built in HTML Dialog element that is built for this purpose. Much simpler
Just wanted drop the same. With the view transition API it's also very easy to animate it.
No workarounds required anymore.
Exactly my thoughts
This isn't supported by Safari older than March 2022. I wouldn't use Dialog yet.