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);
Note: this is a hard requirement, not an optional nicety like it is for most other components. Skip this line and the markup below just sits there with no hover behavior at all — nothing shows, ever.

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>
Note: a popover's default trigger is 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.

Next: Head to Scrollspy, which — unlike this lesson — goes back to needing only a couple of data attributes.