Tooltips, Popovers & Toasts

Bootstrap · Lesson 23 of 28 7 min read Updated

Most Bootstrap components start working as soon as you add their data-bs-* attributes. Three are different: tooltips, popovers and toasts are opt-in. For performance reasons, Bootstrap does not search the page for them, so you must start them with a line of JavaScript. Forgetting this step is the number one reason "my tooltip does not work".

In this lesson you will initialise all three, choose placements and triggers, make popovers that close on the next click, and show stacked toast notifications.

Tooltips

A tooltip is a small dark label that appears when the user hovers over or focuses an element, such as an icon button. Mark the element with data-bs-toggle="tooltip" and the text in data-bs-title, then initialise every tooltip on the page:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Tooltips</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-5 text-center">
    <div class="d-flex flex-wrap gap-2 justify-content-center mb-4">
      <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Shown above">Top</button>
      <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Shown to the right">Right</button>
      <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Shown below">Bottom</button>
      <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Shown to the left">Left</button>
    </div>
    <p>
      Your <a href="#gst" data-bs-toggle="tooltip" data-bs-title="Goods and Services Tax, 18% on this service">GST</a>
      amount is ₹270.
    </p>
    <span class="d-inline-block" tabindex="0" data-bs-toggle="tooltip" data-bs-title="Fill in your PIN code first">
      <button type="button" class="btn btn-primary" disabled>Check delivery</button>
    </span>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    // Tooltips are opt-in: create one Tooltip instance per trigger element.
    document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el) => new bootstrap.Tooltip(el));
  </script>
</body>
</html>

What to know about tooltips:

  • data-bs-placement can be top (the default), right, bottom or left. Bootstrap (through Popper) flips the tooltip if there is no room.
  • They open on hover and keyboard focus, so put them only on elements that can be focused: links, buttons and form fields. A tooltip on a plain <span> cannot be reached with the keyboard unless you add tabindex="0".
  • A disabled button ignores mouse events, so its tooltip never shows. Wrap it in a focusable span and put the tooltip on the wrapper, as the "Check delivery" example does.
  • An empty title means no tooltip at all.
  • Bootstrap also adds aria-describedby to the trigger while the tooltip is shown, so screen readers read the tooltip text after the element's name. Still, keep important information visible on the page, because touch-screen users cannot hover.

For pages that add elements later (for example, rows loaded with fetch), you can initialise once on a parent with the selector option, and new matching elements get tooltips automatically:

JavaScript
new bootstrap.Tooltip(document.body, { selector: '[data-bs-toggle="tooltip"]' });

Popovers

A popover is a larger box with an optional title and some content. By default it opens and closes when you click the trigger, so it works on touch screens too. It is initialised in the same way:

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Popovers</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-5">
    <p>
      Bus 47A, Rohtak to Jhajjar
      <button type="button" class="btn btn-sm btn-outline-info" data-bs-toggle="popover"
              data-bs-title="Stops" data-bs-content="Old Bus Stand, Medical More, Sampla, Jhajjar.">Show stops</button>
    </p>
    <p>
      Fare: ₹65
      <a tabindex="0" class="btn btn-sm btn-outline-secondary" role="button" data-bs-toggle="popover"
         data-bs-trigger="focus" data-bs-placement="bottom" data-bs-title="Concession"
         data-bs-content="Students and senior citizens pay half. Carry an ID card.">Concession?</a>
    </p>
    <button type="button" class="btn btn-sm btn-primary" id="seatBtn" data-bs-toggle="popover" data-bs-title="Seats" data-bs-content="Checking...">Seats left</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    document.querySelectorAll('[data-bs-toggle="popover"]').forEach((el) => new bootstrap.Popover(el));

    // Change a popover's content from JavaScript
    const seatBtn = document.getElementById("seatBtn");
    seatBtn.addEventListener("shown.bs.popover", () => {
      bootstrap.Popover.getInstance(seatBtn).setContent({ ".popover-body": "12 seats left on the 6:15 pm bus." });
    });
  </script>
</body>
</html>

The three popovers show three behaviours:

  1. Default (click): opens on click, closes on the next click of the same button. Default placement is right.
  2. Dismiss on next click: data-bs-trigger="focus" closes the popover when the user clicks anywhere else. Bootstrap recommends an <a> with tabindex="0" and role="button" for this, because some browsers (Safari on macOS, for example) do not give a clicked <button> keyboard focus, so a focus trigger would not fire there.
  3. Dynamic content: setContent() replaces parts of the popover. The keys are CSS selectors inside the popover: .popover-header for the title and .popover-body for the content.

Popover text is treated as plain text. You can allow HTML with data-bs-html="true", but Bootstrap then runs it through a sanitizer that removes dangerous tags and attributes. Never turn sanitizing off for content that includes user input.

Toasts

A toast is a small notification that appears in a corner and disappears after a few seconds: "Item added to cart", "Message sent". Toasts are also opt-in: you create and show them from JavaScript.

HTMLTry it yourself
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Toasts</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-4">
    <h1 class="h5">Pune Book Depot</h1>
    <div class="d-flex gap-2">
      <button type="button" class="btn btn-primary" id="addBtn">Add "Gitanjali" to cart</button>
      <button type="button" class="btn btn-outline-danger" id="errorBtn">Simulate an error</button>
    </div>
  </div>

  <!-- Container that stacks toasts in the bottom-right corner -->
  <div class="toast-container position-fixed bottom-0 end-0 p-3">
    <div class="toast" id="cartToast" role="status" aria-live="polite" aria-atomic="true">
      <div class="toast-header">
        <strong class="me-auto">Cart</strong>
        <small class="text-body-secondary">just now</small>
        <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
      </div>
      <div class="toast-body">Gitanjali (₹199) was added. <a href="#cart">View cart</a></div>
    </div>
    <div class="toast align-items-center text-bg-danger border-0" id="errorToast" role="alert" aria-live="assertive" aria-atomic="true"
         data-bs-autohide="false" data-bs-theme="dark">
      <div class="d-flex">
        <div class="toast-body">Payment failed. Please try again.</div>
        <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
      </div>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    const cartToast = bootstrap.Toast.getOrCreateInstance(document.getElementById("cartToast"), { delay: 4000 });
    const errorToast = bootstrap.Toast.getOrCreateInstance(document.getElementById("errorToast"));
    document.getElementById("addBtn").addEventListener("click", () => cartToast.show());
    document.getElementById("errorBtn").addEventListener("click", () => errorToast.show());
  </script>
</body>
</html>

How toasts work:

  • A toast has an optional toast-header and a toast-body. It is hidden until you call show().
  • toast-container with position-fixed bottom-0 end-0 p-3 pins notifications to a corner and stacks several toasts neatly with space between them.
  • By default a toast hides itself after 5000 ms. Change it with the delay option, or keep it until closed with data-bs-autohide="false", as the error toast does. Errors should stay until the user has read them.
  • Accessibility: use role="status" aria-live="polite" for routine messages, so a screen reader announces them when it finishes speaking, and role="alert" aria-live="assertive" for urgent errors, which interrupt. aria-atomic="true" makes the whole toast be read, not just the changed part. The live region (the toast) must be in the page before you show it, so it is announced reliably.
  • For a coloured toast, use text-bg-*. Adding data-bs-theme="dark" to it makes the close button white so it stays visible.

Toasts fire show.bs.toast, shown.bs.toast, hide.bs.toast and hidden.bs.toast, and have isShown() to check their state.

Common mistakes

  • Never initialising them. Tooltips, popovers and toasts do nothing until you create instances in JavaScript.
  • Initialising before the elements exist. Put the script at the end of <body>, or use the selector option for elements added later.
  • Tooltips on disabled buttons or plain text. They never appear for mouse or keyboard users. Use a focusable wrapper.
  • Important information only in a tooltip. Phone users cannot hover. Put essential text on the page.

Key points

  • Tooltips, popovers and toasts are opt-in: create them with new bootstrap.Tooltip(el), new bootstrap.Popover(el) and bootstrap.Toast.getOrCreateInstance(el).
  • Tooltips use data-bs-title and data-bs-placement, open on hover and focus, and need focusable triggers.
  • Popovers open on click by default; data-bs-trigger="focus" closes them on the next click, and setContent() updates them.
  • Toasts go in a fixed toast-container, hide after 5 seconds by default, and use role="status" or role="alert" with aria-live.
  • HTML in tooltips and popovers is sanitized; never disable that for user input.

Exercise

On a product page for a Jaipur handicraft shop, add three icon-style buttons ("Share", "Wishlist", "Compare") with tooltips placed below them. Add a "Size guide" link that opens a popover with the title "Sizes" and the content "S: 36, M: 38, L: 40", closing when the user clicks elsewhere. Finally, clicking "Wishlist" should also show a toast saying "Saved to your wishlist" that hides after 3 seconds.

Show solution
HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Block-print kurta</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <div class="container py-4">
    <h1 class="h5">Block-print cotton kurta, ₹1,150</h1>
    <div class="d-flex gap-2 mb-3">
      <button type="button" class="btn btn-outline-secondary" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Share with friends">Share</button>
      <button type="button" class="btn btn-outline-secondary" id="wishBtn" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Save for later">Wishlist</button>
      <button type="button" class="btn btn-outline-secondary" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Compare with similar items">Compare</button>
    </div>
    <a tabindex="0" role="button" class="link-primary" data-bs-toggle="popover" data-bs-trigger="focus" data-bs-title="Sizes" data-bs-content="S: 36, M: 38, L: 40">Size guide</a>
  </div>
  <div class="toast-container position-fixed bottom-0 end-0 p-3">
    <div class="toast" id="wishToast" role="status" aria-live="polite" aria-atomic="true">
      <div class="toast-body d-flex">
        Saved to your wishlist
        <button type="button" class="btn-close ms-auto" data-bs-dismiss="toast" aria-label="Close"></button>
      </div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
  <script>
    document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach((el) => new bootstrap.Tooltip(el));
    document.querySelectorAll('[data-bs-toggle="popover"]').forEach((el) => new bootstrap.Popover(el));
    const wishToast = bootstrap.Toast.getOrCreateInstance(document.getElementById("wishToast"), { delay: 3000 });
    document.getElementById("wishBtn").addEventListener("click", () => wishToast.show());
  </script>
</body>
</html>

All tooltips and popovers are created at the end of the page, after the elements exist. The size guide uses a focusable <a> with the focus trigger, and the toast instance is created with a 3-second delay.

Quick Quiz

1. You added data-bs-toggle="tooltip" and data-bs-title, but no tooltip appears. What is the most likely reason?

2. What is the default trigger for a popover?

3. How long does a toast stay visible by default?

4. Which attributes suit an urgent error toast?

Learn Bootstrap with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call