JavaScript Components and Data Attributes
How dropdowns, carousels, and collapses are wired up declaratively with data attributes instead of hand-written JS.
2 min de lecture
You've already seen data-bs-toggle and data-bs-target show up on navbars and modals. This lesson looks at the pattern behind them more directly, and covers two more components — dropdowns and carousels — that rely on it.
The pattern: data attributes instead of event listeners
Bootstrap's JavaScript bundle scans the page for elements with data-bs-* attributes and automatically wires up the right behavior — you never call addEventListener yourself for standard usage:
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Edit</a></li>
<li><a class="dropdown-item" href="#">Duplicate</a></li>
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item text-danger" href="#">Delete</a></li>
</ul>
</div>data-bs-toggle="dropdown" is all it takes — Bootstrap's JS handles opening the menu on click, closing it when you click elsewhere, closing it on Escape, and using Popper.js to position it so it doesn't run off the edge of the screen. All of that logic lives in bootstrap.bundle.min.js, which is why the earlier installation lesson stressed loading the bundle, not the plain bootstrap.min.js — dropdowns silently fail to position correctly without Popper included.
Carousels
A carousel cycles through a set of images or slides, and it's built entirely from data-bs-* attributes as well:
<div id="heroCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
<img src="slide1.jpg" class="d-block w-100" alt="Product overview" />
</div>
<div class="carousel-item">
<img src="slide2.jpg" class="d-block w-100" alt="Customer dashboard" />
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#heroCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#heroCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>data-bs-ride="carousel" starts it auto-playing on page load; data-bs-slide="prev"/"next" on the control buttons connect them to the carousel by matching data-bs-target to its id, the same targeting pattern used everywhere else. .visually-hidden on the "Previous"/"Next" text hides it visually while keeping it available to screen readers, since the buttons would otherwise have no accessible name beyond a decorative arrow icon.
When to drop down to the JavaScript API
Data attributes cover declarative, click-triggered behavior well, but real applications often need to trigger a component from other code — showing a toast after an API call succeeds, for instance:
const toastEl = document.getElementById("saveToast");
const toast = new bootstrap.Toast(toastEl);
toast.show();Every component with data-attribute support (Modal, Dropdown, Collapse, Carousel, Toast, Tooltip, and others) has a matching JavaScript class with the same name, giving you .show(), .hide(), and component-specific methods. Reach for the JS API when the trigger is something other than a direct user click; use data attributes for everything else — they're less code, and there's nothing to keep in sync if the markup changes.
Initializing tooltips and popovers manually
A few components are opt-in for performance reasons — Bootstrap doesn't scan for every tooltip on every page automatically:
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el) => {
new bootstrap.Tooltip(el);
});If a tooltip or popover isn't appearing despite the correct markup, this initialization step — easy to forget — is usually why.