This article covers building a modal window with jQuery, and the newer approach that needs almost no JavaScript at all.
The short answer: use the HTML <dialog> element. Escape-to-close, the backdrop, focus trapping and scroll locking all come built in. If jQuery is already on the page, or you need precise control over the open and close behaviour, the jQuery version below still applies.
Both are covered so you can pick the one that fits.
Sponsored
The modal we are building
See the Pen
jQuery modal window by rin (@rinblog0408)
on CodePen.
The HTML structure
<div id="myModal" class="modal">
<div class="modal-content">
<div>
<p>Modal content goes here</p>
</div>
<button id="closeModalBtn">Close</button>
</div>
</div>
<button id="openModalBtn">Open the modal</button>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
Check the jQuery version you load. Versions before 3.5.0 carry two XSS vulnerabilities (CVE-2020-11022 and CVE-2020-11023). On a public site, load 3.5.1 or later.
Sponsored
The CSS
.modal {
display: none; /* hidden initially */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
padding: 20px;
}
Opening and closing with jQuery
$(document).ready(function() {
$("#openModalBtn").on("click", function() {
$("#myModal").show();
});
$("#closeModalBtn").on("click", function() {
$("#myModal").hide();
});
});
Three things this is missing
That code produces something that looks like a modal but does not behave like one.
▼What is missing
| Missing behaviour | Consequence |
|---|---|
| Escape does not close it | No way to dismiss it from the keyboard |
| Clicking the backdrop does nothing | Users hunt for the close button |
| Focus escapes to the page behind | Tab moves out of the modal entirely |
The first two are straightforward to add.
$(document).ready(function() {
const $modal = $("#myModal");
function openModal() {
$modal.show();
// move focus into the modal
$modal.find("button").first().trigger("focus");
}
function closeModal() {
$modal.hide();
// return focus to the trigger
$("#openModalBtn").trigger("focus");
}
$("#openModalBtn").on("click", openModal);
$("#closeModalBtn").on("click", closeModal);
// close when the backdrop is clicked
$modal.on("click", function(e) {
if (e.target === this) closeModal();
});
// close on Escape
$(document).on("keydown", function(e) {
if (e.key === "Escape" && $modal.is(":visible")) closeModal();
});
});
The e.target === this check matters. Without it, clicking anything inside the modal also closes it.
Trapping focus inside the modal is considerably more work to write correctly. If you need that, the <dialog> element gets you there faster.
Sponsored
The dialog element needs almost no JavaScript
The HTML <dialog> element handles Escape, the backdrop, focus management and scroll locking for you.
<dialog id="myDialog">
<p>Modal content goes here</p>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
<button id="openDialogBtn">Open the modal</button>
const dialog = document.getElementById("myDialog");
document.getElementById("openDialogBtn").addEventListener("click", () => {
dialog.showModal();
});
Three lines of JavaScript.
Use showModal(), not show(). Both display the dialog, but only showModal() makes the rest of the page inert — which is what “modal” means.
showModal(): the page behind becomes inert (modal)show(): the page behind stays interactive (modeless)
Wrapping the close button in <form method="dialog"> closes the dialog with no JavaScript at all.
Style the backdrop with the ::backdrop pseudo-element:
#myDialog {
border: none;
border-radius: 8px;
padding: 20px;
}
#myDialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
To close on a backdrop click, add a few lines:
dialog.addEventListener("click", (e) => {
if (e.target === dialog) dialog.close();
});
The dialog’s own content sits inside it, so e.target is only the dialog itself when the backdrop was clicked.
Which to choose
Use <dialog> for new work, and the jQuery version when adding to an existing site.
▼The two approaches compared
<dialog> |
CSS + jQuery | |
|---|---|---|
| JavaScript | Three lines | Open, close, Escape, backdrop |
| Escape to close | Built in | You write it |
| Backdrop | ::backdrop |
You style it |
| Focus trapping | Built in | Hard to get right |
| Scroll locking | Built in | You write it |
| Fine-grained control | Limited | Complete |
Focus is where the gap is widest. A hand-rolled modal lets Tab move into the page behind it, which leaves keyboard users in a dialog they cannot dismiss and content they cannot reach. <dialog> prevents that automatically.
The same show-and-hide reasoning appears in building an accordion menu and building a jQuery dropdown menu, both of which also cover the native HTML alternatives.
For walking from a clicked element to the thing it controls, getting parent elements in jQuery explains when .closest() is the right tool.