
Quick answer: A Bootstrap 5 modal is a dialog built from three classes — modal, modal-dialog and modal-content — opened by a trigger with data-bs-toggle="modal" and data-bs-target="#id". The JavaScript is already in bootstrap.bundle.min.js, so no extra code is needed to open, close or animate it. The three mistakes that break most modals are a missing bundle script, a data-bs-target that does not match the dialog’s id, and Bootstrap 4 code that still uses the data-toggle prefix.
If you have not added the framework yet, start with getting started with Bootstrap 5 — this guide assumes the two CDN lines are already on the page.
The complete modal in 3 steps
Everything a working dialog needs: a trigger button, the modal markup, and a close button. No custom JavaScript at all.
<!-- Step 1: the trigger -->
<button type="button" class="btn btn-primary"
data-bs-toggle="modal" data-bs-target="#deleteModal">
Delete record
</button>
<!-- Step 2: the modal markup (place anywhere in the body) -->
<div class="modal fade" id="deleteModal" tabindex="-1"
aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Delete this record?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
This action cannot be undone.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<!-- Step 3: the bundle (already required for the navbar) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>Two attributes do most of the work. data-bs-toggle="modal" tells Bootstrap the element opens a dialog, and data-bs-target points at its id. The fade class on the wrapper adds the CSS transition; leave it off for an instant, un-animated open. Because the trigger is a button, pressing Enter on a keyboard works with no extra markup.
Modal options that matter
| Option | Attribute | What it does |
|---|---|---|
| Static backdrop | data-bs-backdrop="static" | Clicking outside will not close it |
| Ignore keyboard | data-bs-keyboard="false" | Esc will not close it |
| Vertical center | modal-dialog-centered | Centers the dialog in the viewport |
| Scrollable body | modal-dialog-scrollable | Long content scrolls inside the dialog |
| Small / large / XL | modal-sm / modal-lg / modal-xl | Width of the dialog |
| Fullscreen | modal-fullscreen | Covers the whole viewport, or use modal-fullscreen-sm-down |
The size classes go on the modal-dialog div, not the outer wrapper — the most common reason a modal refuses to grow. Combine them with centering for a login box: <div class="modal-dialog modal-dialog-centered modal-sm">.
Opening and closing from JavaScript
Trigger attributes cover the click case, but sometimes you need to open a dialog from code — after a form submits, on a timer, or from a button rendered at runtime. Create the instance once and call its methods:
// grab (or create) the instance
const modalEl = document.getElementById('deleteModal');
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
modal.show(); // open
modal.hide(); // close
modal.toggle(); // flip state
modal.dispose(); // remove listeners (single-page apps)
// react to the lifecycle
modalEl.addEventListener('hidden.bs.modal', () => {
console.log('dialog finished closing');
});The event chain is show.bs.modal → shown.bs.modal → hide.bs.modal → hidden.bs.modal. Use show.bs.modal to inject fresh data and hidden.bs.modal to clear it, so the next open never shows stale values. Prefer the option form when you need a static backdrop: new bootstrap.Modal(el, { backdrop: 'static', keyboard: false }).
When the modal does not open: check these four things
- No bundle script. Modals are JavaScript-driven. If only the CSS
linkis loaded, the button does nothing. data-bs-target/idmismatch.#deleteModalmust equalid="deleteModal"exactly — one typo and the click is ignored.- Bootstrap 4 attributes.
data-toggleanddata-targetwere renamed todata-bs-toggleanddata-bs-targetin v5. Old snippets silently do nothing. - Two copies of Bootstrap. Loading the bundle twice, or mixing a bundled copy with a separate Popper build, throws errors that stop every component on the page.
Coming from the other big framework? The Materialize modal guide covers the same component with an init-based API — a useful side-by-side if you support both. A modal that collects input usually wraps a form; the field markup from our Bootstrap forms and validation guide drops straight into modal-body.
Want an instructor to review your dialog-heavy interfaces with you? Ampersand Academy teaches front-end development one-to-one, using your own project as the curriculum.
Frequently asked questions
Why is my Bootstrap 5 modal not opening?
Almost always one of three things: the bootstrap.bundle.min.js script is missing so no JavaScript wires the trigger, the data-bs-target value does not exactly match the modal’s id, or the markup still uses the Bootstrap 4 data-toggle prefix. Check all three in that order.
How do I stop a Bootstrap modal from closing when I click outside it?
Add data-bs-backdrop to the modal wrapper with the value static, or pass the backdrop option set to static when creating it in JavaScript. Add data-bs-keyboard set to false as well if you also want to disable the Esc key.
How do I open a Bootstrap modal with JavaScript?
Get an instance with bootstrap.Modal.getOrCreateInstance(element) and call its show() method. The same object exposes hide() and toggle(). Create it once and reuse it rather than building a new instance on every click.
How do I change the size of a Bootstrap modal?
Add modal-sm, modal-lg or modal-xl to the modal-dialog div. For a dialog that fills the viewport use modal-fullscreen, or a responsive variant such as modal-fullscreen-sm-down to go fullscreen only on small screens.
What is the difference between data-toggle and data-bs-toggle?
data-toggle was the Bootstrap 4 attribute; Bootstrap 5 renamed every component attribute to the data-bs- prefix, so data-bs-toggle is the v5 name. If an old snippet does nothing on Bootstrap 5, the prefix is the reason.
