A lightweight, vanilla JavaScript tooltip library

v2 v1

🎉 Features


The default tooltip looks like this when Tippy is given no options. It has a nifty backdrop filling animation!

Placement compare_arrows

Tooltips can be placed in four different ways in relation to their reference element. Additionally, the tooltip can be shifted.

Arrows play_arrow

Arrows point toward the reference element. There are two different types of arrows: Sharp and Round. You can transform the proportion and scale of the arrows any way you like.

Triggers touch_app

Triggers define the types of events that cause a tooltip to show. A fourth trigger, Manual, is used when you want to programmatically show or hide a tooltip.

Interactivity pan_tool

Tooltips can be interactive, meaning you can hover over or click on them and they won't hide.

Animations blur_on

Tooltips can have different types of transition animations.

Transitions & Delays timer

Tooltips can have different transition durations or delays.

Themes brush

You can create all kinds of custom funky themes for your tooltips with ease.

Callbacks call

Callbacks allow you to react to a tooltip's show and hide events. Open your browser console to see when the logs occur.

HTML code

Tooltips can even contain HTML!

Event delegation settings_input_antenna v2.1

Bind a Tippy instance to a parent container and freely add new child elements without worrying about creating new Tippy instances for them.

Misc tag_faces

Tippy has many more features! These are just some of them.

📦 Getting started

file_download Download (/dist/ folder)

Alternatively, you can install through npm:

npm install --save tippy.js@2.2.3

Or use the unpkg.com CDN:


Simply include the tippy.all.min.js file in your document before your own scripts:

<script src="https://unpkg.com/tippy.js@2.2.3/dist/tippy.all.min.js"></script>

This is the bundled version which includes Popper.js and automatically injects Tippy's CSS stylesheet into the document head.

Use tippy.standalone.js if you want to use a different version of Popper.js.

🗒️ Note

Tippy supports IE10+, but JavaScript in these docs is written in ES2015.

If you want to copy code from these docs, you will need to use Babel to transpile it to ensure support for older browsers.

🔧 Creating a tooltip

First, give your element(s) a title attribute containing what you want the tooltip to say.

<button class="btn" title="I'm a tooltip!">Text</button>
<button class="btn" title="<strong>I'm a bold tooltip!</strong>">Text</button>

Then, to give them a Tippy tooltip, call the tippy() function by passing in a CSS selector.



The title attributes can contain HTML as it gets parsed when setting innerHTML.

Tippify all titled elements

To give all elements with a title attribute a tooltip, you can use:


For details on the type of CSS selector string you can use, see document.querySelectorAll() for reference.

Additional selector options

You aren't limited to just a CSS selector string as input. You can also directly use a DOM element (or an array of elements):


Or a NodeList:



For more specialized cases, you can also pass in a custom virtual object instead of a DOM node to act as the positioning reference.

const refObject = {
  attributes: {
    title: 'Tooltip text'
  getBoundingClientRect() {
    return {
      top:  5 + window.pageYOffset,
      left: 5 + window.pageXOffset,
      right: 5 + window.pageXOffset,
      bottom: 220 + window.pageYOffset,
      width: 1,
      height: 1
  get clientWidth() {
    return 30
  get clientHeight() {
    return 30


Elements without a title attribute (or an empty title) and without an HTML template will not receive a tooltip.


When you invoke tippy(), it modifies the reference element targets.


<button title="Tooltip">My element</button>


<button data-tippy data-original-title="Tooltip">My element</button>
  • title attribute is removed
  • data-tippy empty attribute is added
  • data-original-title attribute is added

Additionally, for a11y, when the tooltip has fully transitioned in, an aria-describedby attribute is added.

⚙️ Customizing tooltips

tippy() takes an object as a second argument for you to customize the tooltips being created. Here's an example:

tippy('.btn', {
  placement: 'right',
  animation: 'scale',
  duration: 1000,
  arrow: true


Data attributes

You can also specify options on the element itself by adding data-tippy-* attributes. This overrides any global options specified in the instance.

  class="btn tippy"
  title="I'm a tooltip!"
  data-tippy-duration="[275, 250]"


This is helpful if you want to globally define options, but make a few tooltips different without having to call tippy() again with different options.

🔮 All options

Note: options with camelCase are lowercase in HTML. For example, animateFill is data-tippy-animatefill in HTML.

Optionsettings Defaultremove Inputssettings_input_component Rolebuild
placement 'top' 'top' 'bottom' 'left' 'right' Specifies which direction to place the tooltip in relation to the reference element. Add the suffix -start or -end to shift the placement. 'top-end' is an example.
livePlacement v2.2 true Boolean If false, the tooltip won't flip when scrolling. This was unconfigurable and always true before v2.2.
trigger 'mouseenter focus' 'mouseenter' 'focus' 'click' 'manual' {custom} Specifies which type of events will trigger a tooltip to show. Separate each by a space. mouseenter is for hovering and touch on mobile, and focus is for keyboard navigation. Use manual if you want to show/hide the tooltip manually (see the Methods section below). {custom} refers to the fact that you can have any event listener, but it won't have the opposite "hide" event.
dynamicTitle false Boolean Whenever the title attribute on the reference element changes, the tooltip will automatically be updated.
interactive false Boolean Makes a tooltip interactive, i.e. will not close when the user hovers over or clicks on the tooltip. This lets you create a popover (similar to Bootstrap) when used in conjunction with a click trigger.
interactiveBorder 2 Number (pixels) Specifies the size of the invisible border around an interactive tooltip that will prevent it from closing. Only applies to mouseenter triggered tooltips.
animation 'shift-away' 'shift-away' 'shift-toward' 'perspective' 'fade' 'scale' Specifies the type of transition animation a tooltip has.
animateFill true Boolean Adds a material design-esque filling animation. This is disabled if you have arrow set to true.
arrow false Boolean Adds an arrow pointing to the reference element. Setting this to true disables animateFill.
arrowType 'sharp' 'sharp' 'round' Specifies the type of arrow to use. Sharp is a CSS triangle, whereas Round is a custom SVG shape.
arrowTransform '' Transform CSS Allows you to transform the arrow, such as the proportion using scale.

Because of flipping, the syntax becomes dynamic. You must use the syntax that gives the desired results for the top placement, even if you use a different placement. Only translate and scale are supported for dynamic syntax.

'scaleX(1.5)' = wider arrow
'scaleX(0.5)' = narrower arrow
'scale(0.5)' = smaller arrow
'scale(1.5)' = larger arrow
'translateY(-5px)' = arrow closer to tooltip
'translateY(5px)' = arrow farther from tooltip
target v2.1 null String | null A CSS selector string used for event delegation. When specified, it will make the element a delegate. The selector should match the child elements that should receive a tooltip.
delay 0 Number | Array (milliseconds) Specifies how long it takes after a show or hide event is fired for a tooltip to begin showing or hiding. Use an array to specify a different show and hide delay, such as [300, 100].
flip true Boolean Specifies whether the tooltip should flip (the reversing of placement based on the amount of room in the viewport to display a tooltip).
flipBehavior 'flip' 'flip' 'clockwise' 'counterclockwise' | Array of placement strings Specifies the flipping behavior of a tooltip. Based on the amount of room in the viewport, the tooltip will choose which placement to use. For example, 'clockwise' with a placement of 'right' will flip to the bottom when there is not enough room.
maxWidth '' String with unit Specifies the maximum width of a tooltip. Ensure you add units, such as px, rem, etc.
duration [350, 300] Number | Array (milliseconds) Specifies how long the transition animation takes to complete. A single number will use the same duration for the show and hide events. Use an array to specify a different show and hide duration, such as [300, 100].
html false false | template id | Element Allows you to add HTML to a tooltip. See Creating HTML templates.
size 'regular' 'small' 'regular' 'large' Specifies how large the tooltip is.
distance 10 Number (pixels) Specifies how far away the tooltip is from its reference element. This contrasts the offset option in that it only applies to a single axis and allows tooltips to still be interactive when their trigger is mouseenter.
theme 'dark' 'dark' You can create your own easily. See Creating themes.
offset 0 Number | String (pixels) Offsets a tooltip on a certain axis. Use a string such as '25, 10' to offset it on both the x and y axes.
hideOnClick true true false 'persistent' Specifies whether to hide a tooltip upon clicking its reference element after hovering over and when clicking elsewhere on the document. For click-triggered tooltips when using false, toggle functionality remains unless you use 'persistent'.
multiple false Boolean Specifies whether to allow multiple tooltips open on the page (click trigger only).
followCursor false Boolean Specifies whether to follow the user's mouse cursor (mouse devices only).
inertia false Boolean Modifies the transition-timing-function with a cubic bezier to create a "slingshot" intertial effect.
updateDuration 350 Number (milliseconds) Specifies the transition duration between flips and when updating a tooltip's position on the document.
sticky false Boolean Specifies whether the tooltip should stick to its reference element when it's showing (for example, if the element is animated/moves).
appendTo document.body Element | Function Specifies which element the tooltip popper is appended to. Use a function which returns an Element for more advanced use cases.
zIndex 9999 Number Specifies the z-index of the tooltip popper.
touchHold false Boolean Changes the trigger behavior on touch devices. It will change it from a tap to show and tap off to hide, to a tap and hold to show, and a release to hide.
performance false Boolean Disables data-tippy-* attribute options to make initial instantiation time faster.
onShow - Function Callback function triggered when a tooltip begins to show.
onShown - Function Callback function triggered when a tooltip has fully transitioned in.
onHide - Function Callback function triggered when a tooltip begins to hide.
onHidden - Function Callback function triggered when a tooltip has fully transitioned out.
createPopperInstanceOnInit false Boolean By default, the popper instance for a tooltip is not created until it is shown for the first time in order to optimize performance. In some cases this may cause issues, so you can specify it to be created when you init with tippy().

Safari seems to stutter slightly when the tooltip is shown for the first time. Certain CSS effects may also cause the transition to stutter on the first show. For these certain cases, you may want to set this option to true.
popperOptions {} Object Allows more control over tooltip positioning and behavior. See right below.

Modify the default options

You can change the default options by accessing them via tippy.defaults, which will apply to every tippy instance.

Finer control over tooltips

You can define a popperOptions option with Popper.js options. View the Popper.js documentation to see the options you can specify.


If you want things to happen at certain times during a tooltip's show/hide events, you can add callback functions in the options object. There are 5 to use:

tippy('.btn', {
  onShow() {
    // When the tooltip has been triggered and has started to transition in
  onShown() {
    // When the tooltip has fully transitioned in and is showing
  onHide() {
    // When the tooltip has begun to transition out
  onHidden() {
    // When the tooltip has fully transitioned out and is hidden
  wait(show, event) {
    // Delays showing the tooltip until you manually invoke `show()`

this inside the callbacks refers to the popper element (if not using arrow functions).

tippy() can create multiple tooltips at once, so your callbacks are reused for each instance. The relevant instance is passed as an argument to the callback.v2.1

tippy('.btn', {
  onShow(instance) {
    // In v2.0.x, `this._reference._tippy` is the instance

AJAX tooltips

Callbacks allow you to do powerful things with tooltips. Here's an example of dynamic content which on show, fetches a new random image from the Unsplash API. Note: this requires a browser which supports the newer fetch API.

Loading a new image...

Codepen demo

Event delegation v2.1

Event delegation only requires minimal setup. Your setup should look similar to this, with a parent element wrapping the child elements you would like to give tooltips to:

<div id="parent" title="Shared title">
  <div class="child">Text</div>
  <div class="child">Text</div>
  <div class="child">Text</div>
  <div class="other">Text</div>

Specify a CSS selector string as the target that matches the child elements which should receive tooltips.

tippy('#parent', {
  target: '.child'

Specify a title attribute on a child element to override the shared title.

<div id="parent" title="Shared title">
  <div class="child" title="Individual title">Text</div>

If the target option is specified, the reference(s) become delegates and receive a data-tippy-delegate attribute instead of data-tippy.

<div id="parent" title="Shared title" data-tippy-delegate></div>

Child targets cannot specify an individual/custom trigger option, as that is the delegate's responsibility.

Destroying delegates

When you destroy a delegate's Tippy instance, it will destroy all target children's Tippy instances as well. To disable this behavior, pass false into the destroy() method.

const parent = document.querySelector('#parent')
tippy(parent, {
  target: '.child'
parent._tippy.destroy(false) // Will not destroy any child target instances (if they had been created)

Tooltips inside a scrollable container

You may encounter issues with tooltip positioning when it's given to an element within a scrollable container. Firstly, when the element is no longer visible, the tooltip will stay stuck within the viewport. Secondly, it transitions between position updates. To solve this, append the tooltip to the reference element's parent and add the following popperOptions:

tippy('.mySelector', {
  appendTo: document.querySelector('.mySelector').parentNode,
  popperOptions: {
    modifiers: {
      preventOverflow: {
        enabled: false
      hide: {
        enabled: false

Blurry tooltips

You may have problems with tooltips appearing blurry in some browsers. This is likely caused by the translate3d transform used by Popper.js to use GPU acceleration to increase performance.

A temporary workaround is to disable gpuAcceleration, but be warned this has a performance impact.

tippy('.mySelector', {
  popperOptions: {
    modifiers: {
      computeStyle: {
        gpuAcceleration: false


Tooltips react to content changes. For example, if a paragraph element inside the tooltip is removed or changed, the tooltip will automatically update its position.

Note: This isn't supported in IE10 natively (unless polyfilled) because it uses MutationObserver.

Disabling tooltips on touch devices

It can be tricky to determine touch devices accurately, especially considering the existence of hybrid devices (a mix of mouse and touch input). Simply detecting the user agent is not enough.

A user can switch between either input type at any time which is why dynamic input detection is enabled. You can hook into Tippy's detection of user input changes by defining the following callback function:

tippy.browser.onUserInputChange = type => {
  console.log('The user is now using', type, 'as an input method')

Whenever the user changes their input method, you can react to it inside the callback function. To disable tooltips for touch input but keep them enabled for mouse input, you can do the following:

const tip = tippy('[title]')

tippy.browser.onUserInputChange = type => {
  const method = type === 'touch' ? 'disable' : 'enable'
  for (const tooltip of tip.tooltips) {

Hiding tooltips on scroll

Due to the way browsers fire mouseleave events, it may be desirable to hide tooltips and immediately disable their event listeners whenever scrolling occurs. This might also help reduce the intrusiveness of a tooltip on small screen touch devices, as it will begin hiding out of the way whenever they scroll, rather than whenever they tap somewhere else.

window.addEventListener('scroll', () => {
  for (const popper of document.querySelectorAll('.tippy-popper')) {
    const instance = popper._tippy

    if (instance.state.visible) {

🏷️ Props

It's important to distinguish between the object returned from calling tippy() and a Tippy instance. When you call tippy(), it can create multiple tooltips (Tippy instances) at once.

Tippy instances refer to individual tooltips, whereas the object returned from tippy() refers to the collection.

tippy() object

const tipObj = tippy('.btn')

tipObj is a plain object.

Keyvpn_key Typememory Descriptiondescription
selector String | Element | NodeList | Object | Array The target references to be given tooltips.
tooltips Array Array of all Tippy instances that were created.
options Object Default + instance options merged.
destroyAll Function Method to destroy all tooltips that were created (all Tippy instances inside tooltips)

Tippy instances

Stored on reference elements via the _tippy property.

const btn = document.querySelector('.btn')
const tipInstance = btn._tippy

tipInstance is a Tippy instance.

Keyvpn_key Typememory Descriptiondescription
id Number The ID of the instance.
titlev2.1 String The content of the tooltip (if not HTML).
popper Element The popper element which contains the tooltip element and content.
popperInstance Popper | null The Popper instance. Not created until the tooltip is shown for the first time, unless specified otherwise.
reference Element The element you gave the tooltip to.
listeners Array Array of objects containing the event type and handler which were bound to the reference element based on the trigger option.
options Object Default + instance + individual options merged together.
state Object Object containing boolean values about the state of the tooltip.
destroyed - has the instance been destroyed?
enabled - is the tooltip enabled?
visible - is the tooltip currently visible and not transitioning out?

For details on Tippy instance methods, go to the Methods section.

🎮 Methods

Suppose you have the following element which has been given a Tippy tooltip:

<button id="myButton" title="Tooltip">My element</button>
const btn = document.querySelector('#myButton')

The Tippy instance is stored on the reference element via the _tippy property:


Tippy instances have 5 methods: enable(), disable(), show(), hide(), and destroy(). This allows you to control the tooltip without the use of UI events.

Show the tooltip


Hide the tooltip


Pass a number in as an argument to specify a custom transition duration in milliseconds:


Destroy the tooltip


Destroying the tooltip will delete the _tippy property from the reference element.

To destroy all the tooltips that were created, use the destroyAll() method on the object returned from tippy():

const tip = tippy('.btn')

tip.tooltips is an array that contains all the individual Tippy instances that were created.

Disable/enable the tooltip

In cases where you want to temporarily disable a tooltip from showing or hiding but NOT destroy it, you can use the disable() method.


To re-enable it:


Update the tooltip

In V1, for tooltips whose content needed to be changed, there was an update() method. This has been replaced by a much easier way.

For standard tooltips, simply set the dynamicTitle option to true.

tippy(btn, {
  dynamicTitle: true

Whenever the title attribute on the reference element changes, the tooltip's content will automatically be updated.

Retrieve the reference element from a popper

The reference element is attached to the popper element via the _reference property.


An example of this being useful is when handling a click event inside the popper element, and needing to know which element it refers to.

The Tippy instance is also attached to the popper element via _tippy.


Get all Tippy instances

Getting all (non-destroyed) Tippy instances on the document can be done in one single line:

Array.from(document.querySelectorAll('[data-tippy]'), el => el._tippy)

This returns an array holding every current Tippy instance (excluding delegates). To include delegates, use this selector:

'[data-tippy], [data-tippy-delegate]'

⚠️ Array.from needs a polyfill for older browsers.

💎 Creating HTML templates

There are two options you have when creating a template. You can either clone it or use it directly.


Use the template's id selector string.

Option: html: '#myTemplate'

  • Can be re-used multiple times
  • Not removed from the page
  • Will not save event listeners attached to it
  • Not directly modifiable


Use Element directly.

Option: html: document.querySelector('#myTemplate')

  • Can only be used once
  • Removed from the page and appended directly to the tooltip
  • Saves event listeners attached to it
  • Directly modifiable

Example HTML template

<div id="myTemplate">
  <p>Fun <strong>non-interactive HTML</strong> here</p>
  <img alt="cat" height="150" src="img/cat.jpg">
tippy('#myElement', {
  html: document.querySelector('#myTemplate'), // Direct element option
  arrow: true,
  animation: 'fade',
  distance: 15,
  arrowTransform: 'scale(2)'


Updating tooltips with HTML content

For HTML tooltips, save your template to a variable in order to modify it later.

const template = document.querySelector('#myTemplate')
tippy(btn, {
  html: template
template.innerHTML = 'Updated!'

If you want to re-use the template multiple times, use cloneNode(true):

const template = document.querySelector('#myTemplate')
const clonedTemplate = template.cloneNode(true)
tippy(btn, {
  html: clonedTemplate
clonedTemplate.innerHTML = 'Updated!'
// template.innerHTML remains unaffected

Dynamically pulling HTML content

You can use a function instead of an element reference as the html setting. The function will be executed with one argument: the element the tooltip is being added to. This is useful for cases in which you have multiple elements, each one having its own unique tooltip.

tippy('.myElements', {
  html: el => el.querySelector('.popup')

Styling tooltips with HTML content

Use this selector to target your tooltip template. Replace "#my-template-id" with your own template's id, including the leading hash. If you're using a DOM element in the html setting and no id is found, it defaults to tippy-html-template.

.tippy-tooltip[data-template-id="#my-template-id"] {
  /* Your styling here. Example: */
  padding: 2rem;

Interactive elements also receive a class of tippy-active upon triggering so that you can still style hover effects when the user has moved the cursor away from the tooltipped element and onto the tooltip itself.

.my-tooltipped-element:hover, .my-tooltipped-element.tippy-active {
  /* Your hover styling here. */

🖌️ Creating themes

Creating a theme for your tooltips is easy. If you wanted to make a theme called honeybee, then your CSS would look like:

.tippy-tooltip.honeybee-theme {
  /* Your styling here. Example: */
  background-color: yellow;
  border: 2px solid orange;

Themes need the -theme suffix.

Style the arrow

To style the arrow, target the element with a tippy-arrow or tippy-roundarrow class:

.tippy-popper[x-placement^=top] .tippy-tooltip.honeybee-theme .tippy-arrow {
  /* Your arrow styling here. */

In this example, the arrow is being styled when the tooltip placement begins with top. You will need to target a specific popper placement (top, bottom, left, right) because the arrow will change based on the placement.

Sharp arrows are CSS triangles which use the border trick, while round arrows are an SVG shape which can have their color changed with fill.

Style content directly

.tippy-tooltip.honeybee-theme .tippy-content {
  /* Your styling here. Example: */
  color: black;

Style the animateFill backdrop

.tippy-tooltip.honeybee-theme .tippy-backdrop {
  /* Your styling here. Example: */
  background-color: yellow;

Specify a theme

tippy('.btn', {
  theme: 'honeybee'

...or specify a data-tippy-theme attribute on the reference element.

<button class="btn tippy" title="I'm a tooltip" data-tippy-theme="honeybee">Honeybee theme</button>

Example custom theme:

Multiple themes

Add multiple themes by separating each by a space.

tippy('.btn', {
  theme: 'menu light'

This will add the classes .menu-theme and .light-theme to the tooltip's class list.

🖥️ Browser support

Tippy gracefully degrades on older browsers (and with JavaScript disabled) by using the browser's default title tooltip.

✅ Supported browsers

Browsers which support requestAnimationFrame. See caniuse. If your audience has low Opera Mini usage (common in western countries), then support should be >96%.

⚠️ IE10

IE10 is only partially supported. Most features work, but dynamicTitle requires a MutationObserver polyfill. Fortunately, IE10 usage is tiny compared to IE11 which is fully supported (0.1% vs 2.8% usage).

📱 Touch devices

Tippy supports touch devices. Standard behavior is to tap an element to show its tooltip, and then tap elsewhere to hide it. It's also possible to change the behavior so that you can tap-and-hold to show it, then release to hide it.

🖐️ Dynamic input detection

For certain options to work reliably depending on the type of user input (such as mouse or touch), dynamic input detection is enabled by default. To disable this behavior, set dynamicInputDetection to false:

tippy.browser.dynamicInputDetection = false

To see Tippy's browser settings:


♿ Accessibility

Tooltips have ARIA labelling to ensure accessibility.

🐛 Browser quirks

Certain browsers are buggy and don't handle things perfectly. Tippy tries to work around them, but here's a known list of issues:

  • iOS browsers don't animate clip-path smoothly, so it is disabled. This only applies to the animateFill backdrop filling animation. IE10+ and Edge don't support it at all yet. The text will overflow when transitioning if the text is not the same color as the background.
  • UC Browser disables border-radius with the backdrop filling animation.


You can have thousands of tooltipped elements without affecting page performance after the initialization. Tooltips are only appended to the DOM when shown, and removed when hidden. Popper.js only listens to scroll and resize events when a tooltip is showing.

Event delegation defers tooltip initialization until it's triggered and is O(1) to init, making it the ideal candidate for performance-critical scenarios.

Note: these tooltips are created with performance mode set to true (but without event delegation).


MIT. Also check Popper.js' license.

This is an example of an interactive HTML tooltip created with a template.