Migrating Bootstrap 4 Modals to Bootstrap 5 Without jQuery
Bootstrap 5 removed jQuery, but your modals still need to work. This guide shows how to migrate a Bootstrap 4 modal to the native Bootstrap 5 API, covering initialization, events, and key trade‑offs.
02 Aug 2025, 17:21 UTC

Problem: jQuery is Gone, but Your Modals Still Need to Work
Many teams built their UIs on Bootstrap 4 and relied on jQuery to initialize components like modals, tooltips, and popovers. Bootstrap 5 dropped jQuery entirely, replacing it with a lightweight, native‑JavaScript API. If you have a legacy modal that opens via “$('#myModal').modal('show')”, that call will fail in a v5 project, breaking the user experience.
So the question becomes: how do you migrate a modal to Bootstrap 5 while keeping the same behavior and avoiding a jQuery dependency?
Thesis: Use the Native Bootstrap 5 Modal API and Popper.js
Bootstrap 5 provides a Modal constructor that accepts a DOM element and an optional options object. The constructor internally creates a Popper instance for positioning, so you don’t need to import jQuery or Popper separately – just the Bootstrap bundle or the modal component.
Key Decision Points
- Component Scope: Import only the
modalmodule if you want a smaller bundle. - Event Names: Bootstrap 5 uses
show.bs.modalandhide.bs.modalinstead of the jQuery‑style.on('show.bs.modal', …)syntax. - Data Attributes: You can still use
data-bs-toggleanddata-bs-targetto trigger the modal without JS.
Concrete Example: From jQuery to Native
Below is a minimal migration example. The HTML remains the same; only the JS changes.
<!-- Bootstrap 5 Modal Markup (identical to v4) -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#demoModal">Open Modal</button>
<div class="modal fade" id="demoModal" tabindex="-1" aria-labelledby="demoModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="demoModalLabel">Demo Modal</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">Modal body text goes here.</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
/* Import Bootstrap's modal module (or the full bundle) */
import { Modal } from 'bootstrap';
// Find the modal element
const modalEl = document.getElementById('demoModal');
// Instantiate the modal – this replaces $('#demoModal').modal()
const demoModal = new Modal(modalEl, {
backdrop: true, // default
keyboard: true, // default
});
// Optional: programmatically show the modal
// demoModal.show();
// Event listeners use addEventListener instead of jQuery .on()
modalEl.addEventListener('show.bs.modal', () => {
console.log('Modal is about to be shown');
});
modalEl.addEventListener('hide.bs.modal', () => {
console.log('Modal is about to be hidden');
});
Running this code in a browser console or bundler will open the modal exactly the same as before, but without any jQuery.
Trade‑Offs and Limitations
- Browser Support: The native API uses
classListandaddEventListener, which are fully supported in all modern browsers but not in IE11. If you still need to support legacy browsers, you’ll need a polyfill or retain jQuery. - Event Namespaces: jQuery’s event namespacing (e.g.,
.on('show.bs.modal.namespace', …)) is not available. You must manage listeners manually or use a small wrapper. - Popper.js Version: Bootstrap 5 bundles Popper 2 automatically. If you import Popper separately for other components, ensure it’s the same major version to avoid conflicts.
- Tree‑Shaking: Importing only the
modalmodule reduces bundle size, but you must also import the required CSS or use a build pipeline that extracts only the needed utilities.
Actionable Checklist for Migration
- Verify the current Bootstrap version:
npm ls bootstrapor check the CDN URL. - Replace all
$('#element').modal('show')calls withnew Modal(element).show(). - Switch event listeners from
.on('show.bs.modal', …)toelement.addEventListener('show.bs.modal', …). - Remove jQuery from the bundle and update any other components that relied on it.
- Run a visual regression test on modal interactions to confirm identical behavior.
- Use DevTools to inspect the modal’s
classListandstylefor correctdisplayandvisibilityvalues. - If you use a build tool, configure PurgeCSS or Vite’s
cssUnocssto keep only the modal CSS.
By following this path you keep the same UI feel while aligning with Bootstrap 5’s modern, dependency‑free architecture.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.