DEV Community

Cover image for πŸš€ how to Create a Modal Component without Javascript Framework
Salah Eddine Lalami for IDURAR | Where Ai Build Software

Posted on • Updated on

πŸš€ how to Create a Modal Component without Javascript Framework

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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
  1. The outsideClick function is responsible for handling clicks outside the modal. If the click target is not inside the modal, it triggers the closeModal function to close the modal.

  2. The escKey function checks if the Escape key was pressed (keyCode 27), and if so, it calls the closeModal function.

  3. 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 the closeModal function.

  4. 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 array FOCUSABLE_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.
  1. 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.

  2. 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 the width and height properties in the option object.
    • close(): This method simply calls the closeModal 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");
Enter fullscreen mode Exit fullscreen mode

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" });
Enter fullscreen mode Exit fullscreen mode

To close the modal, simply call the close method:

modal.close();
Enter fullscreen mode Exit fullscreen mode

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 πŸš€ ?  !
Enter fullscreen mode Exit fullscreen mode

Github Repo : https://github.com/idurar/erp-crm

Open Source 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)

Collapse
 
jonrandy profile image
Jon Randy πŸŽ–οΈ • Edited

...or just use the built in HTML Dialog element that is built for this purpose. Much simpler

Collapse
 
woldtwerk profile image
Willi

Just wanted drop the same. With the view transition API it's also very easy to animate it.
No workarounds required anymore.

Collapse
 
eioluseyi profile image
Emmanuel Imolorhe

Exactly my thoughts

Collapse
 
pau1phi11ips profile image
Paul Phillips

This isn't supported by Safari older than March 2022. I wouldn't use Dialog yet.