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.
| Class | Role |
|---|---|
.modal | The outer wrapper. Hidden by default; Bootstrap's JS toggles it to visible. |
.modal-dialog | Controls the dialog's width, positioning, and centering. |
.modal-content | The actual white box: border, border-radius, and shadow live here. |
.modal-header | Title plus the close (×) button, with a bottom border. |
.modal-body | The main content area — text, a form, whatever the dialog needs. |
.modal-footer | Action 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>
×) 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:
| Class | Max width |
|---|---|
.modal-sm | 300px |
| (default, no size class) | 500px |
.modal-lg | 800px |
.modal-xl | 1140px |
<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:
| Class | Effect |
|---|---|
.modal-dialog-scrollable | Long body content scrolls inside the modal instead of scrolling the whole page. |
.modal-dialog-centered | Vertically 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">
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.