Create a Custom Modal Without Libraries

Modals are versatile elements used to display information or interact with users without navigating away from the current page. They’re commonly found in websites and applications for purposes like alerts, forms, confirmations, notifications, or even displaying multimedia content. Their ability to focus the user’s attention makes them a valuable tool in a developer’s toolkit.

While popular JavaScript libraries and frameworks like Bootstrap offer built-in modals, these can often add unnecessary complexity or bloat to your project. Relying on external libraries may limit your customization options, especially when you need a lightweight solution tailored to your specific design or functionality needs. By building a custom modal from scratch, you gain full control over its appearance and behaviour, allowing for seamless integration and optimal performance.

Step 1: Set Up the HTML Structure

To start, create the foundational HTML structure for the modal. This includes the modal container, its content, and the trigger button to open the modal. The structure is simple and can be extended later for more complex designs or features.

The modal structure consists of three main components:

  1. Modal container (div#modal): Acts as a wrapper that covers the entire screen, ensuring the modal remains visible and centered.
  2. Modal content (div.modal-content): Contains the actual information or elements displayed in the modal.
  3. Close button (span.close-button): Allows the user to close the modal.

Also include a button outside the modal to trigger it. This button simulates how users might open the modal in a real-world scenario.

<div id="modal" class="modal">
    <div class="modal-content">
        <span class="close-button">&times;</span>
        <p>This is a custom modal!</p>
    </div>
</div>
<button id="openModal">Open Modal</button>

Tips

  • While the provided structure is simple, you can enhance accessibility by using ARIA roles, such as role="dialog" for the modal container and aria-labelledby or aria-describedby for better screen reader support.
  • Start with only what’s necessary, then expand or refine the structure as needed when adding new features or content.

Step 2: Add CSS Styles for the Modal

With the HTML structure ready, it’s time to style the modal to make it functional and visually appealing. The CSS styles will handle the modal's positioning, its appearance, and how it behaves when displayed.

The modal container will overlay the entire screen, creating a backdrop that grabs the user’s attention. The content will be centered and styled to look clean and professional. Also style the close button for easy interaction.

/* Modal container */
.modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stays in place even when scrolling */
    top: 0;
    left: 0;
    width: 100%; /* Covers the full width of the viewport */
    height: 100%; /* Covers the full height of the viewport */
    background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent background */
    z-index: 1000; /* Ensures it stays above other elements */
}

/* Modal content */
.modal-content {
    background: #fff; /* White background for the content */
    margin: 10% auto; /* Centers the modal vertically and horizontally */
    padding: 20px; /* Adds spacing inside the modal */
    width: 50%; /* Adjust width as needed */
    border-radius: 8px; /* Smooth rounded corners */
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Subtle shadow for depth */
}

/* Close button */
.close-button {
    float: right; /* Aligns to the top-right corner of the modal */
    font-size: 20px; /* Increases size for visibility */
    cursor: pointer; /* Changes cursor to indicate clickability */
}

Tips

  • Adjust the width and margin of .modal-content for smaller screen sizes using media queries. For example:
@media (max-width: 600px) {
    .modal-content {
        width: 90%; /* Make modal narrower on small screens */
    }
}
  • Add transitions or animations (e.g., fade-in or slide-in effects) to make the modal appear smoothly. You can use CSS transitions like transition: opacity 0.3s; for this purpose.
  • Ensure text and background colours have sufficient contrast, especially for users with visual impairments.

Step 3: Write the JavaScript for Modal Functionality

Now that the HTML and CSS are set up, it’s time to add interactivity. JavaScript will handle the logic for opening and closing the modal, as well as responding to user actions like clicking outside the modal content to close it.

This script will:

  1. Open the Modal: When the user clicks the "Open Modal" button, the modal becomes visible.
  2. Close the Modal: Users can close the modal by clicking the close button or anywhere outside the modal content.
  3. Event Listeners: JavaScript adds interactivity by responding to user events, such as button clicks and window clicks.
const modal = document.getElementById("modal");
const openButton = document.getElementById("openModal");
const closeButton = document.querySelector(".close-button");

// Open modal
openButton.addEventListener("click", () => {
    modal.style.display = "block"; // Show the modal
});

// Close modal
closeButton.addEventListener("click", () => {
    modal.style.display = "none"; // Hide the modal
});

// Close modal when clicking outside content
window.addEventListener("click", (event) => {
    if (event.target === modal) { // Check if the click is outside the content
        modal.style.display = "none"; // Hide the modal
    }
});

Tips

  • Ensure that event listeners are correctly targeting elements to avoid unexpected behavior, such as clicks on other parts of the page affecting the modal.
  • Check for scenarios like:
    • Multiple quick clicks on the open/close button.
    • Attempting to close the modal by clicking outside the content.
    • Verifying responsiveness and usability across devices.

This setup can be extended with additional features like animations, keyboard navigation (e.g., closing with the Escape key), or dynamically populating modal content.

Step 4: Test and Debug the Modal

It's important to test and debug it to ensure a seamless user experience. Testing involves evaluating how the modal behaves across different devices, browsers, and user scenarios. Debugging helps fix any issues that might arise during these tests.

Test for

Accessibility

  • Keyboard Navigation:
    • Ensure users can open and close the modal using the keyboard (e.g., pressing Tab to focus elements and Escape to close).
    • Add a focus trap to keep the tab navigation within the modal while it is open.
    • Use ARIA roles, such as role="dialog" for the modal and aria-labelledby or aria-describedby for associated labels. Example:
<div id="modal" class="modal" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
    <div class="modal-content">
        <h2 id="modalTitle">Custom Modal</h2>
        <span class="close-button">&times;</span>
        <p>This is a custom modal!</p>
    </div>
</div>
  • Screen Reader Compatibility:
    • Use semantic HTML and ARIA attributes to ensure the modal is announced properly by screen readers.

Responsiveness

  • Test the modal’s appearance and functionality across various screen sizes (e.g., desktops, tablets, and smartphones).
  • Use media queries to adjust the modal’s layout for smaller screens.
@media (max-width: 600px) {
    .modal-content {
        width: 90%; /* Fit better on smaller screens */
    }
}

Smooth Animations

  • Add transitions to make the modal’s appearance and disappearance more visually appealing:
.modal {
    opacity: 0;
    transition: opacity 0.3s ease-in-out;
}

.modal.show {
    display: block;
    opacity: 1;
}

Update JavaScript to toggle a show class instead of directly modifying display:

openButton.addEventListener("click", () => {
    modal.classList.add("show");
});

closeButton.addEventListener("click", () => {
    modal.classList.remove("show");
});

Debugging

  • Common Issues:
    • Modal not closing properly when clicking outside or pressing the Escape key.
    • Elements outside the modal becoming interactable while the modal is open (fix this by disabling background scrolling using overflow: hidden on the body).
    • Text or elements overflowing in smaller viewports.
  • Check the modal in multiple browsers (Chrome, Firefox, Safari, Edge) to catch any inconsistencies.
  • Use browser developer tools to inspect styles, debug JavaScript errors, and simulate different devices.
  • Ensure the modal works flawlessly across scenarios, including opening, closing, resizing the browser, and navigating via keyboard.
  • Verify that animations, if added, feel smooth and responsive.

Bonus: Add Features

Once the modal is functional, you can enhance its usability and user experience with additional features. These improvements make the modal more polished, accessible, and visually appealing.

Adding Animation Using CSS (Fade In/Out)

Animations make the modal’s appearance and disappearance smoother, enhancing user experience. Use CSS transitions to add a fade effect.

Add transition and opacity rules to the .modal class. Use opacity for the fade effect and control visibility with the display property:

.modal {
    display: none;
    opacity: 0;
    transition: opacity 0.3s ease-in-out;
}

.modal.show {
    display: block; /* Ensure the modal is visible */
    opacity: 1; /* Fully visible */
}

.modal.hide {
    opacity: 0; /* Fades out */
}

Modify the JavaScript to toggle show and hide classes for fade in/out effects:

openButton.addEventListener("click", () => {
    modal.classList.remove("hide");
    modal.classList.add("show");
});

closeButton.addEventListener("click", () => {
    modal.classList.remove("show");
    modal.classList.add("hide");
    setTimeout(() => {
        modal.style.display = "none"; // Ensure it's fully hidden after fade-out
    }, 300); // Match the transition duration
});

Enhancing Accessibility with ARIA and Focus Trap

Make your modal accessible to screen readers by adding ARIA roles and attributes:

<div id="modal" class="modal" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
    <div class="modal-content">
        <h2 id="modalTitle">Custom Modal</h2>
        <span class="close-button">&times;</span>
        <p>This is a custom modal!</p>
    </div>
</div>
  • role="dialog": Indicates the modal’s purpose to screen readers.
  • aria-labelledby: Links the modal to its title for clear context.
  • aria-hidden: Toggles between true (hidden) and false (visible) to indicate modal visibility.

Ensure keyboard focus remains within the modal when it is open. Add a function to trap focus within the modal elements:

const focusableElements = modal.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];

modal.addEventListener("keydown", (event) => {
    if (event.key === "Tab") {
        if (event.shiftKey) { // Shift+Tab: focus previous
            if (document.activeElement === firstFocusable) {
                event.preventDefault();
                lastFocusable.focus();
            }
        } else { // Tab: focus next
            if (document.activeElement === lastFocusable) {
                event.preventDefault();
                firstFocusable.focus();
            }
        }
    }
});

Adding Keyboard Support for Closing the Modal

Allow users to close the modal using the Escape key for better accessibility and convenience.

Add an event listener to detect when the Escape key is pressed.

window.addEventListener("keydown", (event) => {
    if (event.key === "Escape" && modal.classList.contains("show")) {
        modal.classList.remove("show");
        modal.classList.add("hide");
        setTimeout(() => {
            modal.style.display = "none";
        }, 300);
    }
});

Update the aria-hidden attribute to true when the modal is closed and false when it’s opened.

const toggleAriaHidden = (isVisible) => {
    modal.setAttribute("aria-hidden", isVisible ? "false" : "true");
};

openButton.addEventListener("click", () => {
    modal.style.display = "block";
    modal.classList.add("show");
    toggleAriaHidden(true);
});

closeButton.addEventListener("click", () => {
    modal.classList.add("hide");
    setTimeout(() => {
        modal.style.display = "none";
        toggleAriaHidden(false);
    }, 300);
});

Resources

Need a Helping Hand with Your Project?

Whether you need continuous support through our Flexible Retainer Plans or a custom quote, we're dedicated to delivering services that align perfectly with your business goals.

Please enter your name

Please enter your email address

Contact by email or phone?

Please enter your company name.

Please enter your phone number

What is your deadline?

Please tell us a little about your project