A highly customizable vanilla JS tooltip & popover library

Tippy's features


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


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


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 define the types of events that cause a tooltip to show.


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


Tooltips can have different types of animations.


A tippy can have different transition durations.


Tooltips can delay showing or hiding* after a trigger.

Event delegation v2.1

Bind a Tippy instance to a parent container and freely add new child elements without needing to create Tippy instances for them.


Tooltips can contain HTML, allowing you to craft awesome interactive popovers.

Look! The tippy logo is inside a tippy.


A tippy can have any kind of theme you want! Creating a custom theme is a breeze.


Tippy has a ton of features, and it's constantly improving.

Option 1

Include this script from the unpkg CDN in your HTML document before your own scripts:

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

Once it's loaded, you'll have access to the tippy module which will allow you to create awesome tooltips!

Option 2

Install using either npm or yarn:

npm install tippy.js
yarn add tippy.js

Then you can import the tippy module:

// Node environment
const tippy = require('tippy.js')
// With a module bundler (webpack/rollup/parcel)
import tippy from 'tippy.js'


Tippy builds a bunch of different files that can be used:

  • tippy.all.js is all dependencies (Tippy + Popper + CSS) in a single file. The CSS is injected into the document head.
  • tippy.js is Tippy + Popper together, without the CSS.
  • tippy.standalone.js is Tippy by itself, without Popper or the CSS.
  • tippy.css is Tippy's CSS stylesheet by itself.

There are also .min versions of the above, which means the file is minified for production use.

1. Add your tooltip content

First, give your reference element(s) a title attribute containing your tooltip content.

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

If you hover over the button, you'll notice the browser's default tooltip (usually the native OS tooltip) appears after a delay.

2. Create a tippy

To give the elements a tippy, you'll need to add in some JavaScript inside script tags on your HTML page just before the closing body tag.



When the tippy() function is invoked and given a CSS selector string, it will find all the elements which match it, check if they have a non-empty titleattribute, and then apply its magic to give them a cool tooltip.


The reference element(s) get modified by Tippy in the following manner:

<!-- Before -->
<button class="btn" title="I'm a tooltip!">Text</button>
<!-- After -->
<button class="btn" data-tippy data-original-title="I'm a tooltip!">Text</button>
  • title attribute is removed
  • data-tippy attribute is added
  • data-original-title attribute is added containing the title string

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

Additional input types

A single DOM Element (or an array of them) will work:


As well as a NodeList:


v2.5 Use tippy.one() if you are creating a single tooltip. This will return the tooltip instance directly, rather than a collection object (because tippy() can create multiple tooltip instances at once).


Tippify all titled elements

Use this selector:



You can use a virtual element as the positioning reference instead of a real element:

const virtualReference = {
  attributes: {
    title: "I'm a tooltip!"
  getBoundingClientRect() {
    return {
      width: 100,
      height: 100,
      top: 100,
      left: 100,
      right: 200,
      bottom: 200
  clientHeight: 100,
  clientWidth: 100


Popper.js uses these properties to determine the position of the tooltip.

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

tippy('.btn', {
  delay: 100,
  arrow: true,
  arrowType: 'round',
  size: 'large',
  duration: 500
  animation: 'scale'


Data attributes

You can also specify options on the reference element itself by adding data-tippy-* attributes. This will override the options specified in the instance.

  title="I'm a Tippy tooltip!"


Below is a list of all possible options you can supply to tippy(). The values are the default ones used, with the different inputs being listed as a comment next to it.

tippy(ref, {
  // Available v2.3+ - If true, HTML can be injected in the title attribute
  allowTitleHTML: true,

  // If true, the tooltip's background fill will be animated (material effect)
  animateFill: true,

  // The type of animation to use
  animation: 'shift-away', // 'shift-toward', 'fade', 'scale', 'perspective'

  // Which element to append the tooltip to
  appendTo: document.body, // Element or Function that returns an element

  // Whether to display the arrow. Disables the animateFill option
  arrow: false,

  // Transforms the arrow element to make it larger, wider, skinnier, offset, etc.
  arrowTransform: '', // CSS syntax: 'scaleX(0.5)', 'scale(2)', 'translateX(5px)' etc.

  // The type of arrow. 'sharp' is a triangle and 'round' is an SVG shape
  arrowType: 'sharp', // 'round'

  // The tooltip's Popper instance is not created until it is shown for the first
  // time by default to increase performance
  createPopperInstanceOnInit: false,

  // Delays showing/hiding a tooltip after a trigger event was fired, in ms
  delay: 0, // Number or Array [show, hide] e.g. [100, 500]

  // How far the tooltip is from its reference element in pixels
  distance: 10,

  // The transition duration
  duration: [350, 300], // Number or Array [show, hide]

  // If true, whenever the title attribute on the reference changes, the tooltip
  // will automatically be updated
  dynamicTitle: false,

  // If true, the tooltip will flip (change its placement) if there is not enough
  // room in the viewport to display it
  flip: true,

  // The behavior of flipping. Use an array of placement strings, such as
  // ['right', 'bottom'] for the tooltip to flip to the bottom from the right
  // if there is not enough room
  flipBehavior: 'flip', // 'clockwise', 'counterclockwise', Array

  // Whether to follow the user's mouse cursor or not
  followCursor: false,

  // Upon clicking the reference element, the tooltip will hide.
  // Disable this if you are using it on an input for a focus trigger
  // Use 'persistent' to prevent the tooltip from closing on body OR reference
  // click
  hideOnClick: true, // false, 'persistent'

  // Specifies that the tooltip should have HTML content injected into it.
  // A selector string indicates that a template should be cloned, whereas
  // a DOM element indicates it should be directly appended to the tooltip
  html: false, // 'selector', DOM Element

  // Adds an inertial slingshot effect to the animation. TIP! Use a show duration
  // that is twice as long as hide, such as `duration: [600, 300]`
  inertia: false,

  // If true, the tooltip becomes interactive and won't close when hovered over
  // or clicked
  interactive: false,

  // Specifies the size in pixels of the invisible border around an interactive
  // tooltip that prevents it from closing. Useful to prevent the tooltip
  // from closing from clumsy mouse movements
  interactiveBorder: 2,

  // Available v2.2+ - If false, the tooltip won't update its position (or flip)
  // when scrolling
  livePlacement: true,

  // The maximum width of the tooltip. Add units such as px or rem
  // Avoid exceeding 300px due to mobile devices, or don't specify it at all
  maxWidth: '',

  // If true, multiple tooltips can be on the page when triggered by clicks
  multiple: false,

  // Offsets the tooltip popper in 2 dimensions. Similar to the distance option,
  // but applies to the parent popper element instead of the tooltip
  offset: 0, // '50, 20' = 50px x-axis offset, 20px y-axis offset

  // Callback invoked when the tooltip fully transitions out
  onHidden(instance) {},

  // Callback invoked when the tooltip begins to transition out
  onHide(instance) {},

  // Callback invoked when the tooltip begins to transition in
  onShow(instance) {},

  // Callback invoked when the tooltip has fully transitioned in
  onShown(instance) {},

  // If true, data-tippy-* attributes will be disabled for increased performance
  performance: false,

  // The placement of the tooltip in relation to its reference
  placement: 'top', // 'bottom', 'left', 'right', 'top-start', 'top-end', etc.

  // Popper.js options. Allows more control over tooltip positioning and behavior
  popperOptions: {},

  // The size of the tooltip
  size: 'regular', // 'small', 'large'

  // If true, the tooltip's position will be updated on each animation frame so
  // the tooltip will stick to its reference element if it moves
  sticky: false,

  // Available v2.1+ - CSS selector string used for event delegation
  target: null, // e.g. '.className'

  // The theme, which is applied to the tooltip element as a class name, i.e.
  // 'dark-theme'. Add multiple themes by separating each by a space, such as
  // 'dark custom'
  theme: 'dark',

  // Changes trigger behavior on touch devices. It will change it from a tap
  // to show and a tap off to hide, to a touch-and-hold to show, and a release
  // to hide
  touchHold: false,

  // The events on the reference element which cause the tooltip to show
  trigger: 'mouseenter focus', // 'click', 'manual'

  // Transition duration applied to the Popper element to transition between
  // position updates
  updateDuration: 350,

  // The z-index of the popper
  zIndex: 9999

Modifying the default options

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

More control over tooltips

Specify a popperOptions property with Popper.js options. View the Popper.js documentation to see all the options you can specify.


If you want things to occur during tooltips' show and hide events, you can specify callback functions in the options object.

tippy(ref, {
  onShow(instance) {
    // When the tooltip begins to transition in
  onShown(instance) {
    // When the tooltip has fully transitioned in
  onHide(instance) {
    // When the tooltip begins to transition out
  onHidden(instance) {
    // When the tooltip has fully transitioned out and is removed from the DOM
  wait(show, event) {
    // Delays showing the tooltip until you manually invoke show()

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.

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>

Then, specify a CSS selector as the target that matches child elements which should receive tooltips

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


Avoid binding a Tippy instance to the body, as mouseover / mouseoff events will constantly fire as the cursor moves over the page. Instead, give it to the nearest possible parent element.

Destroying a delegate instance

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' })
// Will not destroy any child target instances (if they had been created)

If the target option is specified, the parent 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>

Tooltips inside a scrollable container

Add the following options to make the tooltip not stay stuck within the viewport.

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

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) {

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.

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 tip = tippy('.btn')

tip is a plain object.

  // selector that was supplied to tippy()
  selector: '.btn',

  // default + instance options merged together
  options: { ... },

  // Array of all Tippy instances that were created
  tooltips: [Tippy, Tippy, Tippy, ...],

  // Method to destroy all the tooltips that were created
  destroyAll() { }

Tippy instances

Stored on reference elements via the _tippy property, and inside the tooltips array of the tippy() object.

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

tipInstance is a Tippy instance.

  // id of the Tippy instance (1 to Infinity)
  id: 1,

  // Popper element that contains the tooltip
  popper: Element,

  // Popper instance is not created until shown for the first time,
  // unless specified otherwise
  popperInstance: null,

  // Reference element that is the trigger for the tooltip
  reference: Element,

  // Array of objects containing the event + handler function of each trigger
  listeners: [{ ... }, { ... }, ...],

  // Defaults + instance + attribute options merged together
  options: { ... },

  // The state of the tooltip
  state: {
    // Has the instance been destroyed?
    destroyed: false,
    // Is the instance enabled?
    enabled: true,
    // Is the tooltip currently visible and not transitioning out?
    visible: false

  // title content of the tooltip (null if HTML)
  title: 'example'


There are several shortcuts available for accessing the instance.

// The popper element has the instance attached to it:
// As does the reference element (as seen above):
// The popper also has the reference directly attached:

Tippy instances have 5 methods available which allow you to control the tooltip without the use of UI events. They are:

  • Tippy.prototype.show()
  • Tippy.prototype.hide()
  • Tippy.prototype.enable()
  • Tippy.prototype.disable()
  • Tippy.prototype.destroy()

Given the following element with a tooltip:

<button title="Hello!">Text</button>
const btn = document.querySelector('button')

The Tippy instance is stored on the button element via the _tippy property.

v2.5 If you are dealing with a single element/tooltip, you can use tippy.one() method to directly return the instance instead of having to use the _tippy property.

const instance = tippy.one('button')

Show the tooltip


Hide the tooltip


Custom transition duration

Pass a number in as an argument to override the instance option:

btn._tippy.show(200) // 200ms
btn._tippy.hide(1000) // 1000ms

Disable the tooltip

The tooltip can be temporarily disabled from showing/hiding:


To re-enable:


Destroy the tooltip

To permanently destroy the tooltip and remove all listeners from the reference element:


The _tippy property is deleted from the reference element upon destruction.

Update the tooltip

Change the title on the reference element and use the dynamicTitle option:

tippy(btn, { dynamicTitle: true })
btn.title = 'New tooltip :)'

If you're using an HTML template, save it to a variable reference to modify it later.

const template = document.querySelector('template')
tippy(btn, { html: template })
template.textContent = 'New tooltip :)'

There are two ways to create an HTML template: cloning or direct reference.

Option 1: Cloning

Clones the template's innerHTML but does not modify it.

Option: html: '#templateId' selector matching a template on the document

  • Reusable
  • Stays on the page
  • Does not save event listeners attached to it
  • Not directly modifiable

Option 2: Direct reference

Directly appends an element to the tooltip.

Option: html: document.querySelector('#templateId') HTMLElement

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

On the document or in JavaScript somewhere, make a template.


<div id="myTemplate" style="display: none;">
  <h3>Cool <span style="color: pink;">HTML</span> inside here!</h3>

Direct element reference

<div id="myTemplate">
  <h3>Cool <span style="color: pink;">HTML</span> inside here!</h3>

Dynamic element with JS

const myTemplate = document.createElement('div')
myTemplate.innerHTML = '<h3>Cool <span style="color: pink;">HTML</span> inside here!</h3>'

Then specify a html option, choosing one of the choices.

tippy('selector', {
  html: '#myTemplate',
  // ...or...
  html: document.querySelector('#myTemplate'),
  // ...or you can clone a direct element too...
  html: document.querySelector('#myTemplate').cloneNode(true)


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;
  font-weight: bold;
  color: #333;

The -theme suffix is required.

Styling the animateFill backdrop

By default, tippy tooltips have a cool backdrop filling animation, which is just a circle that expands out. Its class name is tippy-backdrop:

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

If you're using the backdrop animation, avoid styling the tooltip directly – just the backdrop.

Styling the arrow

There are two arrow selectors: .tippy-arrow and .tippy-roundarrow. The first is the pure CSS triangle shape, while the second is a custom SVG.

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

You will need to style the arrow for each different popper placement (top, bottom, left, right), which is why the selector is so long.

Styling the content directly

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

Specify a theme option

To see what your cool theme looks like, specify a theme option for tippy:

tippy('.btn', {
  theme: 'honeybee',
  // ...or add multiple themes by separating each by a space...
  theme: 'honeybee bumblebee shadow'

.honeybee-theme, .bumblebee-theme and .shadow-theme are the selectors for this theme list.


Current support (tracked): 96% Global, 99% USA

Tippy supports browsers with requestAnimationFrame and MutationObserver support: See caniuse data.

IE10 is only partially supported unless you polyfill MutationObserver, then it is fully supported. dynamicTitle relies on it.

On a 2016 MacBook Pro 2.6 GHz Skylake, using Chrome 65:

  • Performance mode off: 13 ms per 100 elements
  • Performance mode on: 6 ms per 100 elements
  • Event delegation: <1 ms for 1 element!