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 using the suffix -start
or -end
.
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.
Tooltips can contain HTML, allowing you to craft awesome interactive popovers.
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.
You might be wondering why you should use a 14 kB JS library for tooltips and popovers instead of a CSS solution. Pure CSS tooltips are great for simple tooltips when the reference element is positioned in a certain way, but they:
- Will overflow when the tooltip is large and the reference is close to the window edge
- Can't flip to stay optimally visible within the viewport
- Can't follow the mouse cursor
- Difficult to work with self-closing elements like
img
- JavaScript is required for dynamic HTML content
- JavaScript is required to perform side effects (e.g. AJAX)
In addition, Tippy automatically handles many use cases available declaratively in a simple option API. Options like followCursor
, interactive
, touch
, arrow
, and the on*
lifecycle functions make dealing with tooltips & popovers a breeze.
Include this script from the unpkg CDN in your HTML document before your own scripts:
<script src="https://unpkg.com/tippy.js@3/dist/tippy.all.min.js"></script>
It's recommended to place this at the bottom of the <body>
, or in the <head>
with a defer
attribute.
Install using either npm or yarn:
Then you can import the tippy
module:
import tippy from 'tippy.js'
You'll also need to import Tippy's CSS. With a module bundler like Webpack or Parcel, it can be imported directly:
import 'tippy.js/dist/tippy.css'
If you would like to use Tippy.js as a declarative component, there are wrappers available.
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. This is useful if you are using a CDN and want to use the latest version of Popper.js if the bundled version is outdated, or use Popper itself for other things.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.
Tippy is compatible with browsers with requestAnimationFrame
and MutationObserver
support (IE11+). This means most browsers from 2013 onwards, about 99% of desktop users and 95% of mobile users globally (mainly due to Opera Mini on mobile not being supported).
IE11 requires a classList
polyfill if using an SVG element as the reference.
If you need to support old browsers too, you can set the native title
attribute on desktop. On unsupported mobile browsers (such as Opera Mini), it's best to inline the content next to the reference element.
The code throughout this documentation is making use of new JavaScript features (ES6+) that old browsers don't support (such as IE11). If you're going to copy code from here, make sure to use Babel to transpile it into ES5.
Give your reference element a data-tippy
attribute containing the tooltip content.
<button data-tippy="I'm a tooltip!">Text</button>
When Tippy.js is loaded in the document, it will search for elements with the attribute and give them a tooltip automatically. This means you won't have to touch JavaScript at all.
This technique only works on page load and is designed to be used on simple web pages. If you have dynamically generated elements or are using a view library/framework (React, Vue, Angular), use Method 2 below.
Use the tippy
function.
tippy('button', { content: "I'm a tooltip!" })
Using data-tippy-content
allows you to use the function for common custom configuration while giving each tooltip different content.
<button class="btn" data-tippy-content="Tooltip A">Text</button>
<button class="btn" data-tippy-content="Tooltip B">Text</button>
<button class="btn" data-tippy-content="Tooltip C">Text</button>
A single DOM Element
(or an array of them) will work:
tippy(document.querySelector('.btn'))
As well as a NodeList
:
tippy(document.querySelectorAll('.btn'))
You can use a virtual element as the positioning reference instead of a real element:
const virtualReference = { getBoundingClientRect() { return { width: 100, height: 100, top: 100, left: 100, right: 200, bottom: 200 } }, clientHeight: 100, clientWidth: 100
} tippy(virtualReference, { content: "I'm a tooltip!" })
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 configure the tooltips being created. Here's an example:
<button class="btn">Text</button>
tippy('.btn', { content: "I'm a tooltip!", delay: 100, arrow: true, arrowType: 'round', size: 'large', duration: 500, animation: 'scale'
})
You can also specify options on the reference element itself by adding data-tippy-*
attributes. This will override the options specified in the instance.
Used in conjunction with the Auto Method, you can give elements custom tooltips without ever touching JavaScript.
<button class="btn" data-tippy="I'm a Tippy tooltip!" data-tippy-delay="50" data-tippy-arrow="true" data-tippy-animation="shift-toward"
> Text
</button>
Use the tippy.setDefaults()
method to change the default configuration for tippys. It will apply these settings to every future instance.
tippy.setDefaults({ arrow: true, arrowType: 'round', duration: 0
})
Note that the auto-initializing function is deferred with setTimeout()
, which means you can change the default config before the tooltips are automatically created.
Below is a list of all possible options you can pass to tippy.
Prop | Default | Value | Description |
---|---|---|---|
a11y | true | Boolean | If true , ensures the reference element can receive focus by adding tabindex="0" if the element is not natively focusable like a button . |
allowHTML | true | Boolean | Determines if HTML can be rendered in the tippy. |
animateFill | true | Boolean | Determines if the tippy's background fill should be animated. Disabled if arrow: true . |
animation | "shift-away" | The type of transition animation. | |
appendTo | document.body | The element to append the tippy to. Use a function that returns an element to dynamically append the tippy relative to the reference element.
| |
arrow | false | Boolean | Determines if an arrow should be added to the tippy pointing toward the reference element. |
arrowType | "sharp" | The type of arrow. "sharp" is a CSS triangle using the border method, while "round" is a custom SVG shape. | |
arrowTransform | "" | CSS transform to apply to the arrow. Only scale and translate are supported. It is dynamic. Apply the transform that you would normally give to a "top" placement, even if the placement is different. | |
content | "" | The content of the tippy. | |
delay | [0, 20] | Delay in ms once a trigger event is fired before a tippy shows or hides. Use an array of numbers such as [100, 500] to specify a different value for show and hide. Use null in the array to use the default value, e.g. [null, 50] . | |
duration | [275, 250] | Duration of the CSS transition animation in ms. Use an array of numbers such as [100, 500] to specify a different value for show and hide. Add null in the array to use the default value, e.g. [null, 50] . | |
distance | 10 | Number | How far in pixels the tippy element is from the reference element. Only applies to a single axis and not to the parent popper element, see offset. |
flip | true | Boolean | Determines if the tippy flips so that it is placed within the viewport as best it can be if there is not enough room. |
flipBehavior | "flip" | Determines the order of flipping, i.e. which placements to prefer if a certain placement cannot be used. Use an array such as ["bottom", "left"] to prefer the "left" placement if "bottom" is unavailable. By default, it chooses the opposite axis, i.e. "top". | |
followCursor | false | Determines if the tippy follows the user's mouse cursor while showing. Use the strings "vertical" or "horizontal" to only follow the cursor on a single axis. | |
hideOnClick | true | Determines if the tippy should hide if its reference element was clicked. For click-triggered tippys, using false will prevent the tippy from ever hiding once it is showing. To prevent clicks outside of the tippy from hiding it but still allow it to be toggled, use the string "toggle" . | |
inertia | false | Boolean | Adds an attribute to the tippy element that changes the CSS transition timing function to add an inertial "slingshot" effect to the animation. |
interactive | false | Boolean | Determines if a tippy should be interactive, i.e. able to be hovered over or clicked without hiding. |
interactiveBorder | 2 | Number | Determines the size of the invisible border around a tippy that will prevent it from hiding (only relevant for the hover trigger). Useful to prevent the tippy from accidentally hiding from clumsy cursor movements. |
interactiveDebounce | 0 | Number | A number in ms that debounces the onMouseMove handler which determines when the tippy should hide. |
lazy | true | Boolean | By default, the popperInstance (the positioning engine for the tippy) is lazily created. That is, it's only created when necessary (i.e. triggering the tippy for the first time). Setting this prop to false allows you to access the instance synchronously without needing to show the tippy first. |
livePlacement | true | Boolean | Determines if the popper instance should listen to scroll events. This means it will update the position on scroll. If you don't want the tippy to flip around when scrolling, and the tippy's reference is not in a scrollable container, you can set this to false . |
multiple | false | Boolean | Determines if the reference can have multiple tippy instances. |
offset | 0 | Number | An offset that Popper.js uses to offset the popper element. Can work with both the x and y axis, distinct from distance. |
onHide | noop | Function | Lifecycle function invoked when the tippy begins to transition out. |
onMount | noop | Function | Lifecycle function invoked when the tippy has been mounted to the DOM. |
onShow | noop | Function | Lifecycle function invoked when the tippy begins to transition in. |
onShown | noop | Function | Lifecycle function invoked when the tippy has fully transitioned in. |
performance | false | Boolean | If true, disables data-tippy-* attributes which reduces init execution by half. |
placement | "top" | Positions the tippy relative to its reference element. Use the suffix -start or -end to shift the tippy to the start or end of the reference element, instead of centering it. For example, top-start or left-end . | |
popperOptions | {} | Object | Specify custom Popper.js options. See the Popper.js documentation for more. |
shouldPopperHideOnBlur | (FocusOutEvent) => true | Function | A function that returns a boolean to determine if the popper element should hide if it's blurred (applies only if interactive). If the popper element is blurred (i.e. no elements within it are in focus), the popper is hidden. However, there are cases in which you may need to keep it visible even when not in focus. |
showOnInit | false | Boolean | If true , the tooltip will be shown immediately once the instance is created. If using on page load, use sticky: true because the reference element can move around while the layout gets built by the browser after initialization (unless the layout is guaranteed to be static). |
size | "regular" | The size of the tippy. | |
sticky | false | Boolean | Ensures the tippy stays stuck to its reference element if it moves around while showing. |
target | "" | String | CSS selector used for event delegation. |
theme | "dark" | String | Themes added as classes (each separated by a space) to the tippy's class list, which adds a -theme suffix, i.e. "dark-theme" . |
touch | true | Boolean | Determines if the tippy displays on touch devices. |
touchHold | false | Boolean | Determines trigger behavior on touch devices. Instead of a tap on the reference to show and a tap elsewhere to hide the tippy, the reference must be pressed and held for the tippy to show. Letting go from the screen will hide it. To prevent the mobile context menu from appearing, ensure the element cannot be selected using user-select: none; and/or prevent the default behavior for the contextmenu event. |
trigger | "mouseenter focus" | The events (each separated by a space) which cause a tippy to show. Use manual to only trigger the tippy programmatically. | |
updateDuration | 200 | Number | The transition duration between position updates for the sticky option. Set to 0 to prevent flips from transitioning. |
wait | null | Function | A function that, when defined, will wait until you manually invoke show() when a tippy is triggered. It takes the tippy instance and the trigger event as arguments.
|
zIndex | 9999 | Number | The z-index of the popper element. |
Lifecycle functions allow you to do powerful things with tippys. Here's an example of dynamic content which on show, fetches a new random image from the Unsplash API.
Result:
const INITIAL_CONTENT = 'Loading...' const state = { isFetching: false, canFetch: true
} tippy('#ajax-tippy', { content: INITIAL_CONTENT, async onShow(tip) { if (state.isFetching || !state.canFetch) return state.isFetching = true state.canFetch = false try { const response = await fetch('https://unsplash.it/200/?random') const blob = await response.blob() const url = URL.createObjectURL(blob) if (tip.state.isVisible) { const img = new Image() img.width = 200 img.height = 200 img.src = url tip.setContent(img) } } catch (e) { tip.setContent(`Fetch failed. ${e}`) } finally { state.isFetching = false } }, onHidden(tip) { state.canFetch = true tip.setContent(INITIAL_CONTENT) }
})
Note that if you don't specify the dimensions of the image (width
and height
), the tooltip will be positioned incorrectly once it loads. This is because the position of the tooltip is updated before the image's dimensions become known by the browser.
Improved animation with a height transition
Result:
See the CodePen demo.
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"> <div class="child">Text</div> <div class="child">Text</div> <div class="child">Text</div> <div class="other">Text</div>
</div>
Then, specify a CSS selector as the target
that matches child elements which should receive tooltips
tippy('#parent', { content: 'Shared content', target: '.child'
})
Avoid binding a Tippy instance to the body, as mouseover / mouseout
events will constantly fire as the cursor moves over the page. Instead, give it to the nearest possible parent element.
Add the following options to prevent the tippy from staying stuck within the viewport.
tippy('.mySelector', { popperOptions: { modifiers: { preventOverflow: { enabled: false }, hide: { enabled: false } } }
})
In some cases it may be desirable to hide tooltips when scrolling (for example, on touch devices).
window.addEventListener('scroll', () => tippy.hideAllPoppers())
If you return false
in the onShow
or onHide
lifecycle function, it will cancel the operation. Note that this is synchronous, so it won't wait for an AJAX request, etc.
const template = document.querySelector('#myTemplate')
tippy(ref, { content: template, onShow() { // Don't show if the tippy content contains a <strong> element. if (template.querySelector('strong')) { return false } }, onHide({ props }) { // Don't hide if the tooltip has an arrow. if (props.arrow) { return false } }
})
A tooltip on a button is generally used to convey information before the user decides to click on it. On touch devices, this isn't possible because a tap is required to show the tooltip, which will fire a click event.
On iOS, a tap will show the tooltip but click events won't fire until a second tap. This allows the user to see the tooltip before deciding to click the button. On Android, clicking the button will show the tooltip and also fire a click event.
Depending on your use case, one of these will be preferred, so user agent checking may be needed. If neither behavior is preferred, consider using the touchHold: true
option which allows the user to see the tooltip while pressing and holding the button, but won't fire a click event unless the click appears to be intentional.
const button = document.querySelector('button')
const isIOS = /iPhone|iPad|iPod/.test(navigator.platform) /*==================================================
Make iOS behave like Android (single tap to click)
==================================================*/
button.addEventListener('click', () => { // Your logic
})
tippy(button, { onShow() { if (isIOS) { button.click() } }
}) /*==================================================
Make Android behave like iOS (double tap to click)
==================================================*/
// Useful function for dynamically determining the input type:
// https://github.com/30-seconds/30-seconds-of-code#onuserinputchange
let isUsingTouch = false
onUserInputChange(type => { isUsingTouch = type === 'touch'
}) const tip = tippy.one(button)
button.addEventListener('click', () => { if (isIOS || !isUsingTouch ? true : tip.state.isShown) { // Your logic }
})
When using Tippy.js, there are two types of objects to think about: collections and instances.
Whenever you call tippy()
, you are potentially creating many tippys at once. It returns an object containing information about the tippys you created.
const tipCollection = tippy('.btn')
tipCollection
is a plain object.
{ // Targets that should receive a tippy targets: '.btn', // Default props + options merged together props: { ... }, // Array of all instances that were created instances: [tip, tip, tip, ...], // Method to destroy all the tooltips that were created destroyAll() { ... }
}
Stored on reference elements via the _tippy
property, and inside the instances
array of the collection.
tippy('.btn')
const btn = document.querySelector('.btn')
const tip = btn._tippy
Alternatively, you can use the tippy.one()
method to return the instance directly, because only a single tippy is created.
const tip = tippy.one('.btn')
tip
is also a plain object.
{ // id of the instance (1 to Infinity) id: 1, // Reference element that is the trigger for the tooltip reference: Element, // Popper element that contains the tooltip popper: Element, // Object that contains the child elements of the popper element popperChildren: { ... } // Popper instance is not created until shown for the first time, // unless specified otherwise popperInstance: null, // Instance props + attribute options merged together props: { ... }, // The state of the instance state: { // Has the instance been destroyed? isDestroyed: false, // Is the instance enabled? isEnabled: true, // Is the tooltip currently visible and not transitioning out? isVisible: false, // Is the tooltip currently mounted to the DOM? isMounted: false, // Is the tooltip currently fully showing and not transitioning out or in? isShown: false }, // Also contains methods, which you'll learn in the next section
}
There are a couple of shortcuts available for accessing the instance.
// The popper element has the instance attached to it:
popper._tippy
// As does the reference element (as seen above):
reference._tippy
Tippy instances have 7 methods available which allow you to control the tooltip without the use of UI events.
<button data-tippy="Hello">Text</button>
const btn = document.querySelector('button')
The Tippy instance is stored on the button element via the _tippy
property.
Why is it prefixed with an underscore? Since we're attaching a non-standard property to anElement
, we prefix it with an underscore. In the future, there may exist a realtippy
property of elements that would get overwritten by the library, and real DOM properties are never prefixed with an underscore.
Pass a number in as an argument to override the instance option:
The tooltip can be temporarily disabled from showing/hiding:
To re-enable:
To permanently destroy the tooltip and remove all listeners from the reference element:
The _tippy
property is deleted from the reference element upon destruction.
Pass an object of new props to the set()
method to update the tooltip. The tooltip element will be redrawn to reflect the change.
tip.set({ content: 'New content', arrow: true, duration: 1000, animation: 'perspective'
})
There is a shortcut for directly updating the tooltip content.
tip.setContent('New content')
There are a few static methods on the tippy
function itself. These methods are global and do not affect a single instance.
To use capture phase instead of bubbling for the global click listener, call this method before any calls to tippy()
:
Hide all visible poppers on the page:
Disable animation-related default props:
tippy.disableAnimations()
Set the default props for each new tippy instance:
Create a single tooltip and return the instance directly:
tippy.one(reference, options)
Along with using a string of HTML content, you can provide an HTMLElement
for the content
option.
<button class="btn">Text</button>
<div id="myTemplate"> My HTML <strong style="color: pink;">tooltip</strong> content
</div>
tippy('.btn', { content: document.querySelector('#myTemplate')
})
Result:
Tippy will append the DOM element directly to the tooltip, so it will be removed from the page.
What if you want to reuse it multiple times? There's a DOM method for deep-cloning a node.
const clone = document.querySelector('#myTemplate').cloneNode(true)
To know what selectors to use, it's helpful to understand the structure of a tippy element.
<div class="tippy-popper" x-placement="top"> <div class="tippy-tooltip"> <div class="tippy-content"> My content </div> </div>
</div>
A tippy is essentially three nested div
s.
tippy-popper
is what Popper.js uses to position the tippy. You shouldn't apply any styles directly to this element, but you will need it when targeting a specific placement (x-placement
).tippy-tooltip
is the actual tooltip. Use this to style the tooltip whenanimateFill: false
.tippy-backdrop
is the background fill of the tooltip. Use this whenanimateFill: true
.tippy-content
is anything inside the tooltip.
However, depending on the options you supply, additional elements may exist inside it, such as an arrow or backdrop (default) element.
<div class="tippy-popper" x-placement="top"> <div class="tippy-tooltip"> <div class="tippy-backdrop"></div> <!-- animateFill: true --> <div class="tippy-arrow"></div> <!-- arrow: true --> <div class="tippy-content"> My content </div> </div>
</div>
If you wanted to make a theme called honeybee
, then your CSS would look like:
/* If `animateFill: true` (default) */
.tippy-tooltip.honeybee-theme .tippy-backdrop { background-color: yellow; font-weight: bold; color: #333;
} /* If `animateFill: false` */
.tippy-tooltip.honeybee-theme { background-color: yellow; border: 2px solid orange; font-weight: bold; color: #333;
}
The -theme
suffix is required.
To apply the theme to the tooltip, specify a theme
option without the -theme
suffix:
tippy('.btn', { theme: 'honeybee'
})
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.
You will need to style the arrow for each different popper placement if using the default (sharp) arrow: top, bottom, left, right.
/* Default (sharp) arrow */
.tippy-popper[x-placement^='top'] .tippy-tooltip.light-theme .tippy-arrow { border-top-color: #fff;
}
.tippy-popper[x-placement^='bottom'] .tippy-tooltip.light-theme .tippy-arrow { border-bottom-color: #fff;
}
.tippy-popper[x-placement^='left'] .tippy-tooltip.light-theme .tippy-arrow { border-left-color: #fff;
}
.tippy-popper[x-placement^='right'] .tippy-tooltip.light-theme .tippy-arrow { border-right-color: #fff;
}
/* Round arrow */
.tippy-tooltip.light-theme .tippy-roundarrow { fill: #fff;
}
Tested with 2.6 GHz Skylake MacBook Pro using Chrome 67:
tippy.all.min.js
evaluation time: 5ms- Performance mode off (default): 1.3ms per 10 elements
- Performance mode on: 1ms per 10 elements
- Lazy mode off: 3ms per 10 elements
- Event delegation: <1ms for any number of child elements