Modal

A modal is a dialog box that renders on top of the page and blocks interaction with everything behind it until the user deals with it. Bootstrap 5 modals are opened and closed entirely through data-bs-* attributes — no custom JavaScript required for the common case. We'll build this up using a TrailMap "Confirm trail submission" modal as the running example: the dialog that appears right before a user's new trail write-up actually gets sent in.

Anatomy of a Modal

Every Bootstrap 5 modal is built from the same nested set of classes. Getting the nesting right matters more than with most components — skip a layer and the dialog either won't center properly or won't scroll correctly.

ClassRole
.modalThe outer wrapper. Hidden by default; Bootstrap's JS toggles it to visible.
.modal-dialogControls the dialog's width, positioning, and centering.
.modal-contentThe actual white box: border, border-radius, and shadow live here.
.modal-headerTitle plus the close (×) button, with a bottom border.
.modal-bodyThe main content area — text, a form, whatever the dialog needs.
.modal-footerAction buttons, right-aligned by default, with a top border.
<div class="modal fade" id="trailSubmitModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Confirm Trail Submission</h5>
        <button type="button" class="btn-close"
          data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        Ready to publish this trail write-up?
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn btn-primary">Confirm & Submit</button>
      </div>
    </div>
  </div>
</div>
Note: the close (×) button and both footer dismiss buttons use data-bs-dismiss="modal" — same pattern as opening, just closing instead.

Triggering a Modal: data-bs-toggle and data-bs-target

The modal above is inert markup until something opens it. Bootstrap 5 wires a trigger to a modal purely through attributes: data-bs-toggle="modal" tells Bootstrap this element opens a modal, and data-bs-target points at the modal's id (with a leading #), exactly the way an anchor link points at a fragment.

<button type="button" class="btn btn-primary"
  data-bs-toggle="modal" data-bs-target="#trailSubmitModal">
  Submit Trail
</button>

The value of data-bs-target must match the modal's id exactly, including the #. Bootstrap's bundled JavaScript listens for clicks on any element with data-bs-toggle="modal", reads its data-bs-target, and shows the matching .modal — no onclick handler, no addEventListener call.

Sizing a Modal

Add a size class on .modal-dialog itself, alongside the base class, to change the dialog's max width at wider breakpoints:

ClassMax width
.modal-sm300px
(default, no size class)500px
.modal-lg800px
.modal-xl1140px
<div class="modal-dialog modal-lg"> ... </div>

Scrollable and Vertically Centered Modals

Two more modifiers go on .modal-dialog and can be combined freely with a size class and with each other:

ClassEffect
.modal-dialog-scrollableLong body content scrolls inside the modal instead of scrolling the whole page.
.modal-dialog-centeredVertically centers the dialog in the viewport instead of pinning it near the top.
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
Note: a modal with a static backdrop that ignores outside clicks (won't close unless a button inside it is clicked) is a separate opt-in: data-bs-backdrop="static" on the outer .modal element — useful when a confirmation like this one shouldn't be dismissed by accident.

Worked Example: TrailMap's Confirm Trail Submission Modal

Putting it together: a "Submit Trail" button on TrailMap's trail-editor page opens a centered, medium-width confirmation modal with a static backdrop, so a stray click outside the dialog can't discard the write-up.

<!-- Trigger -->
<button type="button" class="btn btn-primary"
  data-bs-toggle="modal" data-bs-target="#trailSubmitModal">
  Submit Trail
</button>

<!-- Modal -->
<div class="modal fade" id="trailSubmitModal" data-bs-backdrop="static"
  tabindex="-1" aria-labelledby="trailSubmitLabel" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="trailSubmitLabel">Confirm Trail Submission</h5>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <p>"Ridgeline Loop" will go live in the public trail directory once submitted.</p>
        <p class="mb-0">You can still edit it afterward from your dashboard.</p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Keep Editing</button>
        <button type="button" class="btn btn-primary">Confirm & Submit</button>
      </div>
    </div>
  </div>
</div>

The whole flow — open on click, block outside dismissal, close on either footer button — works with zero lines of custom JavaScript, purely from the five data-bs-* attributes used above.

Next: Head to Offcanvas for a sliding panel that's brand new in Bootstrap 5.