Modals
A modal is a dialog box that opens on top of the page and dims everything behind it: "Are you sure you want to delete this order?", a login form, a product's quick view. While it is open, the user deals with the modal first. Bootstrap's modal handles the hard parts for you: the dark backdrop, closing with the Escape key, keeping keyboard focus inside the dialog, and returning focus afterwards.
In this lesson you will build a modal, open it with a button and from JavaScript, change its size and behaviour, and fill it with different content depending on which button opened it.
Modal structure
A modal is made of nested divs, each with one job:
| Element | Purpose |
|---|---|
modal fade | The full-screen layer. Hidden until opened. fade adds an animation |
modal-dialog | Positions and sizes the box |
modal-content | The visible white box with border and rounded corners |
modal-header | Title and close button |
modal-body | The main content |
modal-footer | Action buttons, aligned to the right |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Modal</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-4">
<p>Order #4521: 2 kg kaju katli, ₹2,200</p>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#cancelModal">
Cancel order
</button>
</div>
<div class="modal fade" id="cancelModal" tabindex="-1" aria-labelledby="cancelTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="cancelTitle">Cancel this order?</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
The ₹2,200 will be refunded to your UPI account within 3 working days.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Keep order</button>
<button type="button" class="btn btn-danger" id="confirmCancel">Yes, cancel</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.getElementById("confirmCancel").addEventListener("click", () => {
const modal = bootstrap.Modal.getInstance(document.getElementById("cancelModal"));
modal.hide();
document.querySelector(".container p").textContent = "Order #4521 was cancelled.";
});
</script>
</body>
</html>How it works:
- The button's
data-bs-toggle="modal"anddata-bs-target="#cancelModal"open the modal with that id. No JavaScript of your own is needed to open it. - Any element inside with
data-bs-dismiss="modal"closes it: here the X and "Keep order". Clicking the backdrop or pressing Escape also closes it. tabindex="-1"lets the modal itself receive focus, andaria-labelledbypoints at the title so screen readers announce "Cancel this order?, dialog" when it opens.modal-dialog-centeredcentres the box vertically.- The "Yes, cancel" button runs your own code, then closes the modal from JavaScript with
hide().
Place modals near the end of <body>, not inside other components such as a navbar or a card. Their styles and positioning work best at the top level of the page.
Sizes, fullscreen and scrolling
Add classes to modal-dialog to change the box:
Class on modal-dialog | Result |
|---|---|
modal-sm | Narrow, 300px |
| (none) | Default, 500px |
modal-lg | Wide, 800px |
modal-xl | Extra wide, 1140px |
modal-fullscreen | Covers the whole screen |
modal-fullscreen-md-down | Fullscreen only below md (good for long forms on phones) |
modal-dialog-scrollable | Header and footer stay put; only the body scrolls |
modal-dialog-centered | Vertically centred |
The sizes take effect from the sm, lg and xl breakpoints; on a small phone every modal is simply full width with a margin.
Static backdrop: a modal that must be answered
By default, clicking outside the modal closes it. For important steps (accepting terms, a payment in progress) you can stop that. Add data-bs-backdrop="static" and data-bs-keyboard="false" to the modal element:
<div class="modal fade" id="termsModal" data-bs-backdrop="static" data-bs-keyboard="false"
tabindex="-1" aria-labelledby="termsTitle" aria-hidden="true">
...
</div>Now only the buttons inside the modal can close it. When the user clicks outside, Bootstrap gives a small "bounce" animation and fires the hidePrevented.bs.modal event.
The JavaScript API and events
You can control a modal entirely from JavaScript:
const el = document.getElementById("cancelModal");
const modal = bootstrap.Modal.getOrCreateInstance(el, { backdrop: "static" });
modal.show(); // open
modal.hide(); // close
modal.toggle(); // open if closed, close if opengetOrCreateInstance returns the existing modal object or creates one, so you never create two for the same element. Options can also be passed this way instead of as data-bs-* attributes: backdrop (true, false or "static"), keyboard and focus.
Modals fire four main events on the modal element:
| Event | When |
|---|---|
show.bs.modal | Just before opening; event.relatedTarget is the button that opened it |
shown.bs.modal | After it is fully visible (animation finished) |
hide.bs.modal | Just before closing; event.preventDefault() cancels the closing |
hidden.bs.modal | After it is fully hidden |
One modal, different content
A list of students might each have an "Edit" button. You do not need one modal per student. Store the data on the buttons, and fill the modal in the show.bs.modal event:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dynamic modal</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-4">
<ul class="list-group" style="max-width: 420px">
<li class="list-group-item d-flex justify-content-between align-items-center">
Priya Menon, Kochi
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#msgModal" data-name="Priya">Message</button>
</li>
<li class="list-group-item d-flex justify-content-between align-items-center">
Farhan Ali, Lucknow
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#msgModal" data-name="Farhan">Message</button>
</li>
</ul>
</div>
<div class="modal fade" id="msgModal" tabindex="-1" aria-labelledby="msgTitle" aria-hidden="true">
<div class="modal-dialog">
<form class="modal-content" id="msgForm">
<div class="modal-header">
<h1 class="modal-title fs-5" id="msgTitle">New message</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<label for="msgText" class="form-label">Message</label>
<textarea class="form-control" id="msgText" rows="3" required></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary">Send</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
const msgModal = document.getElementById("msgModal");
const text = document.getElementById("msgText");
msgModal.addEventListener("show.bs.modal", (event) => {
const name = event.relatedTarget.dataset.name; // the button that was clicked
document.getElementById("msgTitle").textContent = "Message to " + name;
text.value = "";
});
// autofocus does not work inside modals; focus after the animation instead
msgModal.addEventListener("shown.bs.modal", () => text.focus());
document.getElementById("msgForm").addEventListener("submit", (event) => {
event.preventDefault();
bootstrap.Modal.getInstance(msgModal).hide();
});
</script>
</body>
</html>event.relatedTarget is the button that triggered the modal, so dataset.name reads its data-name. The shown.bs.modal event moves the cursor into the textarea after the opening animation; the autofocus attribute does not work in modals because the modal is hidden when the page loads. Making modal-content a <form> lets the Enter key and the submit button work as in any form.
Common mistakes
- Opening a second modal from inside a modal. Bootstrap supports only one modal at a time. Close the first, or change the content of the same modal.
- Putting the modal inside a
position: fixedelement, such as a fixed navbar. It can appear behind the backdrop. Keep modals at the end of<body>. autofocusin a modal field. It does nothing. Focus the field inshown.bs.modal.- Calling
new bootstrap.Modal(el)every time a button is clicked. You get several instances fighting each other. UsegetOrCreateInstance.
Key points
- A modal is
modal > modal-dialog > modal-contentwithmodal-header,modal-bodyandmodal-footer. - Open it with
data-bs-toggle="modal"anddata-bs-target, close it withdata-bs-dismiss="modal", Escape or the backdrop. - Size and position it with
modal-sm,modal-lg,modal-xl,modal-fullscreen-*,modal-dialog-centeredandmodal-dialog-scrollable. data-bs-backdrop="static"anddata-bs-keyboard="false"force the user to answer.- Use
bootstrap.Modal.getOrCreateInstance(el)and events such asshow.bs.modal(withrelatedTarget) andshown.bs.modalfor dynamic modals.
Exercise
Create a "Book a demo class" button for a coding institute in Jaipur. It opens a large, vertically centred modal containing a form with name, phone and preferred date. The modal must not close when the backdrop is clicked. When it opens, focus the name field. When the form is submitted, close the modal and show a green alert on the page saying "Thanks! We will call you."
Show solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Book a demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container py-4">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#demoModal">Book a demo class</button>
<div class="alert alert-success mt-3" role="alert" id="thanks" hidden>Thanks! We will call you.</div>
</div>
<div class="modal fade" id="demoModal" data-bs-backdrop="static" tabindex="-1" aria-labelledby="demoTitle" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<form class="modal-content" id="demoForm">
<div class="modal-header">
<h1 class="modal-title fs-5" id="demoTitle">Book a free demo class</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body row g-3">
<div class="col-md-6">
<label for="demoName" class="form-label">Name</label>
<input type="text" class="form-control" id="demoName" required>
</div>
<div class="col-md-6">
<label for="demoPhone" class="form-label">Phone</label>
<input type="tel" class="form-control" id="demoPhone" required>
</div>
<div class="col-md-6">
<label for="demoDate" class="form-label">Preferred date</label>
<input type="date" class="form-control" id="demoDate" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Book</button>
</div>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
const demo = document.getElementById("demoModal");
demo.addEventListener("shown.bs.modal", () => document.getElementById("demoName").focus());
document.getElementById("demoForm").addEventListener("submit", (event) => {
event.preventDefault();
bootstrap.Modal.getInstance(demo).hide();
document.getElementById("thanks").hidden = false;
});
</script>
</body>
</html>data-bs-backdrop="static" stops backdrop clicks from closing it, modal-lg modal-dialog-centered sizes and centres it, and the submit handler hides the modal through its instance before revealing the alert. The required fields are checked by the browser before the submit event fires.
Quick Quiz
1. Which element in a modal has the white box with the border?
modal is the full-screen layer, modal-dialog sizes and positions, and modal-content is the visible box.
2. How do you stop a modal from closing when the user clicks outside it?
A static backdrop ignores clicks; add data-bs-keyboard="false" to also ignore Escape.
3. In the show.bs.modal event, what is event.relatedTarget?
It lets one modal show different data depending on which button opened it.
4. Why does autofocus not work on an input inside a modal?
Focus the field after the modal has finished opening.
Learn Bootstrap with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
