What Is a Bootstrap Tooltip?
Hover over an element, or tab onto it, and a short text label shows up beside it. That’s the plugin at work. Popper positions the label, and the text comes from the element’s title or data-bs-title attribute.
It’s Bootstrap’s take on the tooltip pattern in UI design, which means a hint and never the main content. If users need the information to finish a task, it belongs in visible text.
When it renders, the plugin builds a div.tooltip wrapper with role="tooltip". Inside that sit a .tooltip-arrow element and a .tooltip-inner element that holds the text.
None of this markup exists up front. The documentation says tooltips are placed right after the trigger element by default, but in the v5.3.8 source the default container is document.body, so that’s where the tooltip is appended unless you set the container option. Before the first show, the DOM has no trace of it.
The plugin ships with the Bootstrap framework, and this article follows the 5.3 line. The current documentation lists v5.3.8, released on 25 August 2025 (Bootstrap blog).
Older syntax still dominates Bootstrap sites. W3Techs data from 28 October 2026 puts Bootstrap 3 on 28.2% of Bootstrap sites, Bootstrap 4 on 21.4% and Bootstrap 5 on 10.7%.
That gap shows in search results, where several tutorials still teach data-toggle and jQuery initialization. Check the version before copying any snippet.
One more thing it isn’t: a container for rich content. A popover takes a title plus a separate content body, while a tooltip carries a single short label.
How to Add a Bootstrap Tooltip
Load Bootstrap’s CSS and the JavaScript bundle, put data-bs-toggle="tooltip" and a title on the element, then create a bootstrap.Tooltip instance for it. Skip that last step and nothing shows.
How you load the files comes first. The Bootstrap 5.3.8 download page lists these routes:
| Route | What to load | Popper |
|---|
| CDN bundle (jsDelivr) | bootstrap.bundle.min.js | Included |
| CDN, separate files | popper.min.js, then bootstrap.min.js | Load it first |
| npm | npm install bootstrap@5.3.8 | Imported as @popperjs/core |
After that, work through the steps in order:
- Add the Bootstrap CSS link and the bundle script.
- Put
data-bs-toggle="tooltip" on the element. - Give it text with
title or data-bs-title. - Select every trigger with
querySelectorAll. - Pass each one to
new bootstrap.Tooltip(), after the elements exist in the DOM.
<button type="button" class="btn btn-secondary"
data-bs-toggle="tooltip" data-bs-placement="top"
data-bs-title="Tooltip on top">
Hover me
</button>
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(el => new bootstrap.Tooltip(el))
</script>
For a single element, the constructor also takes a selector, as in new bootstrap.Tooltip('#example'). It uses querySelector, so it only ever finds one element.
Either title or data-bs-title works. With title, the documentation says the attribute is replaced by data-bs-title when the element renders (the v5.3.8 source does something slightly different, covered under methods and events below). A zero-length title never displays a tooltip.
Bootstrap 5 doesn’t need jQuery. Plain JavaScript initialization is the documented route, though Bootstrap does add a jQuery plugin interface when it detects jQuery on the page.
Changing Tooltip Placement, Trigger and Delay
Placement uses data-bs-placement, and the trigger event uses data-bs-trigger. Timing goes through data-bs-delay. All of them have defaults, so none is required.
The Bootstrap 5.3 documentation sets these defaults:
- Placement is
top - Trigger is
hover focus - Offset is
[0, 6] (skidding first, then distance) - Show and hide delay are both
0 ms - Animation is
true, which gives a CSS fade
Placement and fallback behavior
Placement takes top, right, bottom or left. There’s also auto, which lets Popper reorient the tooltip to wherever room is left.
When a fixed placement doesn’t fit, Popper flips the tooltip through fallbackPlacements, which defaults to top, right, bottom, left in that order. Directions mirror automatically in right-to-left layouts.
The boundary option sets the overflow constraint and defaults to clippingParents. An element reference overrides it, but only from JavaScript.
const tooltip = new bootstrap.Tooltip('#example', {
boundary: document.body,
fallbackPlacements: ['bottom', 'top']
})
Offset can come from a data attribute too, as comma-separated values, for example data-bs-offset="10,20".
Trigger values
Hover fires when the pointer enters the element, so on its own it leaves keyboard users out. Focus fires when the element receives focus, including by tab. Click toggles the tooltip on every click.
Manual hands control to show, hide and toggle calls, and it can’t be combined with another trigger.
To use several triggers, separate them with a space, as the default hover focus does. The docs warn that hover alone produces tooltips keyboard users can’t open, so keep focus unless the same information is available another way.
Delay, animation and container
<button data-bs-toggle="tooltip"
data-bs-delay='{"show":500,"hide":100}'
data-bs-title="Slow to appear">
Delayed tooltip
</button>
A single number applies to show and hide alike, and the object form splits them. Delay is ignored for the manual trigger.
Animation applies a CSS fade and respects the prefers-reduced-motion media query. Set animation: false to remove it entirely.
The container option appends the tooltip to another element. The docs advise container: 'body' for input groups and button groups, where rendering problems can occur, although the v5.3.8 source already defaults to document.body.
How to Set Tooltip Content, HTML and Sanitizer Rules
Text comes from title or data-bs-title. HTML needs data-bs-html="true", and anything the sanitizer doesn’t recognize is stripped before it reaches the page.
HTML content
<button type="button" class="btn btn-secondary"
data-bs-toggle="tooltip" data-bs-html="true"
data-bs-title="<em>Tooltip</em> <u>with</u> <b>HTML</b>">
Tooltip with HTML
</button>
With html set to false (the default), Bootstrap inserts the title as plain text (the docs say innerText, the v5.3.8 source sets textContent), so tags show up as literal characters. Any HTML markup in a title therefore needs the option turned on.
Prefer plain text whenever the content comes from user input. The docs point to OWASP’s cross-site scripting guidance for exactly that case.
Changing content after initialization
Use setContent instead of a second constructor call.
const tooltip = bootstrap.Tooltip.getInstance('#example')
tooltip.setContent({ '.tooltip-inner': 'another title' })
Keys are selectors inside the tooltip template, and values can be a string, an element, a function or null.
The title option also accepts a function, called with the trigger element as this. The template option swaps the base markup, as long as the outer wrapper keeps the .tooltip class and role="tooltip".
Sanitizer and allowList
Sanitization is on by default and removes every tag and attribute that isn’t explicitly allowed.
| Element | Default | Detail |
|---|
| b, em, strong, u, i, p, ul, li | Allowed | Global attributes only |
| a | Allowed | target, href, title, rel, plus global attributes |
| img | Allowed | src, srcset, alt, title, width, height, plus global attributes |
| table, td, script | Removed | Add tables through allowList |
Global attributes, the ones allowed on every permitted element, are class, dir, id, lang, role and any aria-* attribute.
The behavior comes down to sanitize (true by default), allowList and sanitizeFn. For security reasons none of them can be set through data attributes.
const myDefaultAllowList = bootstrap.Tooltip.Default.allowList
// allow table elements
myDefaultAllowList.table = []
// allow td, plus one data attribute on it
myDefaultAllowList.td = ['data-bs-option']
A dedicated library such as DOMPurify can replace the built-in sanitizer through sanitizeFn. Bootstrap states that vulnerabilities caused solely by disabling or modifying sanitization fall outside its security model.
Styling a Bootstrap Tooltip with CSS Variables
Since v5.2.0, tooltips take their colors, width and spacing from CSS variables set on .tooltip. Override a variable under a custom class and you restyle one tooltip without touching the others.
| Variable | Default | Controls |
|---|
| –bs-tooltip-bg | var(–bs-emphasis-color) | Background |
| –bs-tooltip-color | var(–bs-body-bg) | Text color |
| –bs-tooltip-max-width | 200px | Width cap |
| –bs-tooltip-opacity | .9 | Transparency |
Plain CSS covers most changes. Scope the override with data-bs-custom-class, which the docs also describe as the customClass option.
.custom-tooltip {
--bs-tooltip-bg: var(--bs-primary);
--bs-tooltip-color: var(--bs-white);
}
<button type="button" class="btn btn-secondary"
data-bs-toggle="tooltip"
data-bs-custom-class="custom-tooltip"
data-bs-title="Themed with CSS variables">
Custom tooltip
</button>
Option names switch from camelCase to kebab-case in data attributes, which is why customClass becomes data-bs-custom-class.
If you need wider or multi-line text, raise --bs-tooltip-max-width past 200px.
A tooltip on a hyperlink that spans several lines gets centered, which looks odd. Add white-space: nowrap to the link to stop that.
Sass builds get the same values from $tooltip-max-width, $tooltip-opacity and related variables. $tooltip-arrow-color has been deprecated since 5.2.0 in favor of the CSS variables.
Bootstrap Tooltip Methods and Events
Tooltips expose 11 methods and 5 events. Every method is asynchronous, which means it returns when the transition starts and not when it ends.
A call made while the tooltip is mid-transition is ignored. Chain actions through events, not through back-to-back calls.
Visibility goes through show, hide and toggle. enable, disable and toggleEnabled deal with state. dispose ends an instance, and the static getInstance and getOrCreateInstance find or build one. That leaves setContent and update for content and position.
The lifecycle calls are shared by every Bootstrap plugin and together make up its programmatic API. getInstance returns null when no instance exists on the element, so getOrCreateInstance is the safer call inside event handlers.
Tooltips are enabled by default. In the v5.3.8 source, disable only flips a flag that blocks future shows, so a tooltip that’s already open stays open.
When a tooltip opens, the events fire in this order:
show.bs.tooltip fires the moment show runsinserted.bs.tooltip fires once the template is in the DOMshown.bs.tooltip fires after the fade finishes
Closing mirrors it with hide.bs.tooltip and hidden.bs.tooltip. The two events that name an action in progress, show and hide, can be cancelled.
const el = document.getElementById('myTooltip')
el.addEventListener('show.bs.tooltip', event => {
if (el.dataset.locked === 'true') event.preventDefault()
})
One catch from the JavaScript docs. When jQuery is on the page and data-bs-no-jquery isn’t set on the body, Bootstrap emits events through jQuery’s event system, and the docs say to listen with .on or .one in that case.
Calling dispose right after hide gives incorrect results, because hide returns before the transition ends. Wait for hidden instead.
el.addEventListener('hidden.bs.tooltip', () => tooltip.dispose(), { once: true })
tooltip.hide()
Tooltips created through the selector option can’t be disposed one by one on their descendants.
About the title attribute again. The tooltip documentation says a title is replaced by data-bs-title on render, but the v5.3.8 source moves the text to data-bs-original-title, removes title, and lets dispose put it back.
The source marks that attribute as kept for backwards compatibility only. If you inspect the DOM and find no title, that’s expected behavior and not a bug.
Bootstrap 4 vs Bootstrap 5 Tooltip Syntax
Attribute names, initialization, the Popper version, the arrow class and the sanitizer option name all changed between the two versions. A snippet written for one fails quietly on the other.
| Item | Bootstrap 4.6 | Bootstrap 5.3 |
|---|
| Data attributes | data-toggle, data-placement | data-bs-toggle, data-bs-placement |
| Initialization | $(‘[data-toggle=”tooltip”]’).tooltip() | new bootstrap.Tooltip(el) |
| Popper | v1.x (1.16.1 in the docs) | v2.x (@popperjs/core 2.11.8) |
| Arrow class in template | .arrow | .tooltip-arrow |
| Sanitizer option | whiteList | allowList |
Some defaults moved along with the syntax.
boundary was scrollParent and is now clippingParentsoffset was 0 and is now [0, 6]- The single
fallbackPlacement (default flip) became the array fallbackPlacements, default top, right, bottom, left setContent and getOrCreateInstance do not appear in the 4.6 tooltip method list
The migration guide lists v5.0.0 as the release that dropped jQuery, moved Popper to v2 and namespaced every plugin attribute under data-bs-. A global find and replace on tooltip markup covers most of the move, but only on Bootstrap’s own attributes.
Sources disagree on jQuery. W3Schools’ Bootstrap 3 and 4 tooltip tutorials say tooltips must be initialized with jQuery, while the 5.3 docs say jQuery isn’t needed.
Both are right for their own version, so confirm which one you’re on before trusting either. On a v5 page, bootstrap.Tooltip.VERSION returns the plugin version.
The v4.6 documentation now opens with a banner saying Bootstrap 4 has reached end of life. New projects have no reason to start on it.
Are Bootstrap Tooltips Accessible?
Conditionally. Put tooltips only on links and form controls, and add an Escape handler, because the v5.3.8 source ships none.
Bootstrap’s own guidance limits tooltips to elements that are traditionally keyboard-focusable and interactive. Adding tabindex="0" to a plain span creates a tab stop on non-interactive content, and most assistive technologies don’t announce tooltips there.
In the v5.3.8 source, the default template gets role="tooltip". The trigger gets aria-describedby while the tooltip is shown, and it’s removed after the tooltip hides. Bootstrap also copies the title into an aria-label, but only for elements with no text and no aria-label of their own.
The aria-describedby link is how a screen reader ties the tooltip text to its trigger, and it’s one of the ARIA attributes doing the work. The aria-label part has a catch: it reads the title attribute only, so an icon-only button that uses data-bs-title gets no accessible name from Bootstrap.
WCAG 2.1 success criterion 1.4.13 (Level AA) names custom tooltips directly, according to the W3C’s Understanding document. Content shown on hover or focus has to be dismissible, hoverable and persistent, and here is how the default behavior lines up.
| Condition | WCAG 1.4.13 requires | Bootstrap 5.3.8 default |
|---|
| Dismissible | Closes without moving pointer or focus, such as with Esc | No Escape handler in tooltip.js |
| Hoverable | Pointer can move onto the content without it vanishing | Hide listeners sit on the trigger only |
| Persistent | Stays until hover or focus ends, or the user dismisses it | Stays while the trigger is hovered or focused |
That table is a reading of the source, not a compliance statement from Bootstrap, whose tooltip documentation makes no claim either way. The dismissible gap has a short fix.
const tooltipList = [...document.querySelectorAll('[data-bs-toggle="tooltip"]')]
.map(el => new bootstrap.Tooltip(el))
document.addEventListener('keydown', event => {
if (event.key === 'Escape') tooltipList.forEach(tooltip => tooltip.hide())
})
Hoverable has no one-line fix. A click trigger sidesteps it, because the criterion covers content opened by hover or focus.
Honestly, it’s less a patch than a decision. If a page has to meet formal web accessibility requirements, keep anything a user needs to finish a task out of tooltips.
The W3C itself says there are usually more predictable means of adding content to a page, and visible help text is the plainest one. The WAI-ARIA Authoring Practices tooltip pattern is the reference to read before building anything custom.
Why Is a Bootstrap Tooltip Not Working?
Check the console first. Two of these failures throw readable errors, and the rest fail silently.
| Symptom | Documented cause | Fix |
|---|
| Nothing appears, no error | Tooltips were never initialized | Create a bootstrap.Tooltip per element |
| Error: tooltips require Popper | bootstrap.js loaded without Popper | Use the bundle, or load Popper first |
| Error: use show on visible elements | show called on an element whose inline style is display: none | Show the element first |
| No tooltip on late-added elements | Initialization ran before they existed | Initialize after insertion, or delegate |
| Disabled button ignores hover | Disabled elements get no pointer or focus events | Trigger from a wrapper |
| Old attributes ignored | v5 namespaces them under data-bs- | Rename the attributes |
| Tooltip stuck on screen | Element removed while the tooltip was open | Hide, wait for hidden, then remove |
Tooltip on a disabled button
<span class="d-inline-block" tabindex="0"
data-bs-toggle="tooltip" data-bs-title="Disabled tooltip">
<button class="btn btn-primary" type="button" disabled>Disabled button</button>
</span>
A disabled Bootstrap button can’t be focused, hovered or clicked, so the tooltip hangs on a wrapper instead. Give the wrapper tabindex="0" so keyboard users can reach it.
The v4.6 docs add style="pointer-events: none;" to the disabled button. The v5.3 example drops it, and the v5.0.0 migration notes record that disabled buttons now have pointer-events: none by default.
Tooltips on elements added after load
You can create the instance right after the element enters the DOM. Or delegate from a parent by passing the selector option, and Bootstrap applies tooltips to matching descendants, including ones added later.
new bootstrap.Tooltip(document.body, {
selector: '[data-bs-toggle="tooltip"]'
})
The docs warn that the title attribute must not serve as that selector. Removal follows the same rule as disposal: hide the tooltip, wait for hidden.bs.tooltip, then take the element out.
Bootstrap Tooltip FAQ
What is the difference between a Bootstrap tooltip and a popover?
A popover carries a title plus a content body, while a tooltip carries one short label.
The defaults differ as well. Popovers open on click, sit on the right and cap at 276px, whereas tooltips open on hover or focus, sit on top and cap at 200px.
Can I use a Bootstrap tooltip in React?
Not directly. The Bootstrap documentation says its JavaScript isn’t fully compatible with React, Vue or Angular, because Bootstrap and the framework may both mutate the same DOM element.
For React, the docs point to React Bootstrap, which replaces Bootstrap’s JavaScript instead of running beside it.
How do I add a tooltip to an SVG icon?
Wrap the SVG in a link and put the tooltip attributes on the link, not on the SVG.
The official example uses an anchor with the class d-inline-block, data-bs-toggle, data-bs-title and an aria-label, while the SVG inside gets aria-hidden set to true.
Can I set several tooltip options from HTML in one attribute?
Yes, through data-bs-config, an experimental attribute added in Bootstrap 5.2.0 that holds options as a JSON string.
Separate data-bs- attributes override it, and an options object passed in JavaScript overrides both.
How does a Bootstrap tooltip behave on touch screens?
The documentation doesn’t describe touch behavior, so the answer sits in the source. In v5.3.8, show adds empty mouseover listeners to the body’s children on touch-enabled devices, a workaround the code comment ties to broken event delegation on iOS.
Hide removes those listeners again.
Can I import only the tooltip instead of all of Bootstrap’s JavaScript?
Yes, with a bundler or the ES module build. The JavaScript docs demonstrate named imports using Toast from bootstrap.esm.min.js, and Tooltip follows the same pattern.
Popper still has to resolve as @popperjs/core, which an importmap handles in a browser.
What to Use When a Bootstrap Tooltip Falls Short
Information that users need to finish a task belongs in visible text, not in a Bootstrap tooltip. Keep the tooltip for optional hints on links and form controls.
When a hint outgrows one line, escalate in this order:
- Help text printed beside the field
- A click-triggered popover for optional detail
- A modal dialog once the content holds links or inputs
The order tracks accessibility cost. Popover content reaches screen readers as one uninterrupted stream through aria-describedby, and popovers don’t manage focus order, per the Bootstrap 5.3 documentation.
Interactive content belongs in a modal dialog built with Bootstrap, which traps focus so every control stays reachable.
Checked against the Bootstrap 5.3.8 documentation and source on 28 October 2026. Bootstrap 5.3.8 is still the latest v5 release, and the Bootstrap blog says v5.4.0 is still to come. Bootstrap 6 reached its first alpha (6.0.0-alpha.1) on 8 October 2026 with ESM-only JavaScript and no window.bootstrap global, so the snippets here apply to 5.3. Verify tooltip dismissal and touch handling when v5.4.0 ships.