Tooltips & Popovers
Tooltips and popovers are small floating hint boxes that appear near an element on hover or focus — a tooltip is a short one-line label, a popover can carry a title plus a longer body. Both share something with toasts: neither one works from data attributes alone. We'll wire up a TrailMap difficulty icon tooltip and a "?" help-icon popover to see exactly what's required.
Why These Genuinely Require JavaScript
Modals and offcanvas panels open on click, which Bootstrap's global data-attribute
listener can catch automatically. Tooltips and popovers are different: they need to appear
on hover or focus for every matching element on the page, and — unlike
toasts, which you show once from your own code — that could be dozens of small icons at
once. Rather than silently attaching hover listeners to everything with
data-bs-toggle="tooltip" for you, Bootstrap 5 makes initialization an
explicit, required step:
const tooltipEl = document.querySelector('#difficultyIcon');
new bootstrap.Tooltip(tooltipEl);
Tooltips: data-bs-title and data-bs-placement
A tooltip trigger needs data-bs-toggle="tooltip" plus the text itself in
data-bs-title (not the plain HTML title attribute Bootstrap 4
used — v5 renamed it to avoid the browser's own native tooltip fighting with Bootstrap's):
<i class="bi bi-signpost-fill" id="difficultyIcon"
data-bs-toggle="tooltip" data-bs-placement="top"
data-bs-title="Moderate: some elevation, no technical scrambling"></i>
data-bs-placement accepts top, right,
bottom, or left, and Popper (bundled with Bootstrap 5's JS)
automatically flips it if the tooltip would otherwise render off-screen.
Initializing Every Tooltip on the Page
Rather than writing new bootstrap.Tooltip(...) once per icon by hand, loop
over every element carrying data-bs-toggle="tooltip" with a single selector —
this is the standard boilerplate you'll see at the bottom of most Bootstrap 5 pages that
use tooltips at all:
const tooltipTriggers = document.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggers.forEach(function (el) {
new bootstrap.Tooltip(el);
});
Popovers: Title Plus Body
A popover is initialized the same way but with bootstrap.Popover, and it adds
a data-bs-content attribute for the longer body text alongside
data-bs-title for the heading:
<button type="button" class="btn btn-sm btn-outline-secondary rounded-circle" id="trailHelpIcon"
data-bs-toggle="popover" data-bs-placement="right"
data-bs-title="Difficulty ratings"
data-bs-content="Easy, Moderate, and Strenuous are based on distance, elevation gain, and terrain.">
?
</button>
<script>
const popoverTriggers = document.querySelectorAll('[data-bs-toggle="popover"]');
popoverTriggers.forEach(function (el) {
new bootstrap.Popover(el);
});
</script>
click, not hover — the opposite default from a tooltip. Add data-bs-trigger="hover focus" if you want it to open on hover instead.Worked Example: TrailMap Difficulty Icon and Help Popover
Each trail card shows a small difficulty icon (a plain tooltip) next to a "?" icon that explains the whole rating system (a popover with more room to explain):
<!-- Difficulty icon: quick one-line tooltip on hover -->
<span class="me-2">
<i class="bi bi-signpost-fill" id="difficultyIcon"
data-bs-toggle="tooltip" data-bs-placement="top"
data-bs-title="Moderate: some elevation, no technical scrambling"></i>
</span>
<!-- Help icon: click-triggered popover with more detail -->
<button type="button" class="btn btn-sm btn-outline-secondary rounded-circle" id="trailHelpIcon"
data-bs-toggle="popover" data-bs-placement="right"
data-bs-title="Difficulty ratings"
data-bs-content="Easy, Moderate, and Strenuous are based on distance, elevation gain, and terrain.">
?
</button>
<!-- One initialization block covers both, run once the page loads -->
<script>
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) {
new bootstrap.Tooltip(el);
});
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
new bootstrap.Popover(el);
});
</script>
Two different components, one shared rule: without that closing <script>
block, both icons on every trail card would render as plain, silent HTML.