Modals

Bootstrap · Lesson 18 of 28 7 min read Updated

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.

A modal is made of nested divs, each with one job:

ElementPurpose
modal fadeThe full-screen layer. Hidden until opened. fade adds an animation
modal-dialogPositions and sizes the box
modal-contentThe visible white box with border and rounded corners
modal-headerTitle and close button
modal-bodyThe main content
modal-footerAction buttons, aligned to the right
HTMLTry it yourself
<!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" and data-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, and aria-labelledby points at the title so screen readers announce "Cancel this order?, dialog" when it opens.
  • modal-dialog-centered centres 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-dialogResult
modal-smNarrow, 300px
(none)Default, 500px
modal-lgWide, 800px
modal-xlExtra wide, 1140px
modal-fullscreenCovers the whole screen
modal-fullscreen-md-downFullscreen only below md (good for long forms on phones)
modal-dialog-scrollableHeader and footer stay put; only the body scrolls
modal-dialog-centeredVertically 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:

HTML
<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:

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 open

getOrCreateInstance 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:

EventWhen
show.bs.modalJust before opening; event.relatedTarget is the button that opened it
shown.bs.modalAfter it is fully visible (animation finished)
hide.bs.modalJust before closing; event.preventDefault() cancels the closing
hidden.bs.modalAfter 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:

HTMLTry it yourself
<!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: fixed element, such as a fixed navbar. It can appear behind the backdrop. Keep modals at the end of <body>.
  • autofocus in a modal field. It does nothing. Focus the field in shown.bs.modal.
  • Calling new bootstrap.Modal(el) every time a button is clicked. You get several instances fighting each other. Use getOrCreateInstance.

Key points

  • A modal is modal > modal-dialog > modal-content with modal-header, modal-body and modal-footer.
  • Open it with data-bs-toggle="modal" and data-bs-target, close it with data-bs-dismiss="modal", Escape or the backdrop.
  • Size and position it with modal-sm, modal-lg, modal-xl, modal-fullscreen-*, modal-dialog-centered and modal-dialog-scrollable.
  • data-bs-backdrop="static" and data-bs-keyboard="false" force the user to answer.
  • Use bootstrap.Modal.getOrCreateInstance(el) and events such as show.bs.modal (with relatedTarget) and shown.bs.modal for 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
HTML
<!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?

2. How do you stop a modal from closing when the user clicks outside it?

3. In the show.bs.modal event, what is event.relatedTarget?

4. Why does autofocus not work on an input inside a modal?

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