JavaScript Behavior
|
This section documents Bootstrap 5.x as implemented by the official Bootstrap project. No specific patch version is pinned. Unlike the other reference sections on this site, no single reference book underpins it: the content was generated with the assistance of AI from general knowledge of Bootstrap, and should be verified against the current official documentation at getbootstrap.com/docs before relying on it in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Interactive Bootstrap components — modals, dropdowns, tooltips, offcanvas panels, collapses, carousels, toasts — need JavaScript to open, close, and animate, in addition to the CSS that styles them. Bootstrap 5 exposes two
ways to drive that behavior: declarative data-bs-* attributes for the common case, and a full JavaScript API
for programmatic control. This page covers both, the library’s dependency model, and the custom events every
stateful component dispatches as it changes state.
Declarative behavior via data-bs-* attributes
For the majority of uses — a button that opens a modal, a link that toggles a collapse — no JavaScript needs
to be written at all. Bootstrap’s own bundled script watches the document for clicks on elements carrying
data-bs-toggle, and wires up the target component automatically:
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Launch modal
</button>
<div class="modal" id="exampleModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">Modal body content.</div>
</div>
</div>
</div>
data-bs-toggle="modal" identifies the component type, data-bs-target="#exampleModal" identifies which
instance of it, and data-bs-dismiss="modal" on the close button closes it — three attributes, zero lines of
custom script. The same pattern covers every other stateful component:
<!-- Dropdown -->
<button class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown">Menu</button>
<!-- Collapse -->
<button class="btn" data-bs-toggle="collapse" data-bs-target="#details">Details</button>
<div class="collapse" id="details">Collapsible content.</div>
<!-- Tooltip -- requires explicit JS initialization, see below -->
<button data-bs-toggle="tooltip" data-bs-title="Helpful text">Hover me</button>
Tooltips and popovers are a deliberate exception: for performance, they are not auto-initialized on page
load the way modals and collapses are (instantiating one per element would be wasteful on a page with many
candidates), so they still need one explicit JavaScript call, shown below, even though they use the same
data-bs-* attribute convention for their configuration.
Programmatic control via the JavaScript API
Every component that supports the data-bs-* pattern is backed by a JavaScript class of the same name — bootstrap.Modal, bootstrap.Dropdown, bootstrap.Collapse, bootstrap.Tooltip, and so on — constructible
directly for cases the declarative attributes can’t express: opening a modal in response to a fetch completing,
showing a toast from an error handler, or initializing every tooltip on a page in one pass.
import { Modal, Tooltip } from "bootstrap";
// Open a modal programmatically, e.g. after an async operation
const modalEl = document.getElementById("exampleModal");
const modal = new Modal(modalEl);
modal.show();
// Explicit tooltip initialization -- required, not automatic
const tooltipTriggers = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggers.forEach((el) => new Tooltip(el));
getOrCreateInstance() is the safe way to retrieve a component instance that may already have been created
(by the declarative attribute path or an earlier script), instead of risking a second, conflicting instance on
the same element:
import { Modal } from "bootstrap";
const modalEl = document.getElementById("exampleModal");
const modal = Modal.getOrCreateInstance(modalEl);
modal.hide();
Every component exposes the same small, predictable method surface — typically show()/hide()/toggle()
and dispose() to tear down the instance and detach its listeners — so switching from the declarative
attributes to the JS API for one particular interaction doesn’t require learning a different vocabulary.
Dependencies: no jQuery, vanilla JS plus Popper.js
Bootstrap 4 and earlier required jQuery as a hard dependency for every interactive component. Bootstrap 5 dropped it entirely — every component is implemented in plain, framework-free JavaScript, which removes a sizeable dependency and its associated overhead for a project that doesn’t otherwise need jQuery.
The one dependency Bootstrap 5 still pulls in is Popper.js, used exclusively for positioning the floating elements that need to stay attached to a reference element while avoiding the viewport edge: dropdown menus, tooltips, and popovers. Popper is not needed for modals, collapses, offcanvas panels, toasts, or carousels, none of which have to dynamically reposition themselves relative to a trigger.
Two build outputs reflect this split:
| File | Contents |
|---|---|
|
Every Bootstrap component, without Popper bundled in — use this if Popper is already loaded separately (e.g. as its own dependency, or shared with another library on the page). |
|
The same, with Popper bundled in — the simplest choice for a project with no other reason to manage Popper as a separate dependency. |
<!-- Simplest path: one script tag, Popper included -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
Component events
Every stateful component dispatches a pair of custom DOM events around each state transition — one just
before the change starts, one just after it finishes (including its CSS transition, where applicable) — following the naming convention {event}.bs.{component}:
const modalEl = document.getElementById("exampleModal");
modalEl.addEventListener("show.bs.modal", (event) => {
// fires immediately when show() is called, before the modal is visible
console.log("Modal about to open", event.relatedTarget);
});
modalEl.addEventListener("shown.bs.modal", () => {
// fires once the modal is fully visible and its CSS transition has completed
document.getElementById("modal-input")?.focus();
});
modalEl.addEventListener("hidden.bs.modal", () => {
// fires once the modal has fully closed -- safe point to reset form state
modalEl.querySelector("form")?.reset();
});
The -ing/-ed-style pairing (show / shown, hide / hidden) is consistent across every component that
has an open/close lifecycle — show.bs.collapse/shown.bs.collapse, show.bs.dropdown/shown.bs.dropdown,
show.bs.offcanvas/shown.bs.offcanvas — which is what makes it practical to hook a single generic listener
pattern (e.g. autofocusing the first input) across every dismissible component in a project rather than writing
one-off logic per component type. The "before" event (show.bs.modal) is also cancelable via
event.preventDefault(), letting application code veto the transition — for example, blocking a modal from
opening until an unsaved-changes check passes.
Event flow: from a click to a dispatched event
with data-bs-toggle=modal"] PARSE["Bootstrap's delegated
click listener parses
data-bs-* attributes"] INSTANCE["Modal.getOrCreateInstance()
on the target element"] SHOWEVT["show.bs.modal dispatched
(cancelable)"] DOM["DOM updated:
display set, backdrop inserted,
CSS transition runs"] SHOWNEVT["shown.bs.modal dispatched
(transition complete)"] CLICK --> PARSE --> INSTANCE --> SHOWEVT --> DOM --> SHOWNEVT classDef core fill:#3f51b5,stroke:#1a237e,color:#fff class INSTANCE core
The same flow applies whether the trigger was a data-bs-toggle click or a direct new Modal(el).show()
call — the declarative attribute path is a thin layer that ends up calling the identical JavaScript API
underneath, which is why both approaches dispatch the same show.bs.modal/shown.bs.modal events and can be
mixed freely within one project.