Tutorial
In this tutorial you'll learn how to use Popper by building a basic tooltip.
Remember, Popper is not a tooltip library, it's the foundation to build one! If what you are looking for is a ready to use tooltip lib, take a look at Tippy.js.
Setting up
Create a new HTML document with two elements, a <button>
and a tooltip
<div>
, and pass them to Popper:
<!DOCTYPE html>
<html>
<head>
<title>Popper Tutorial</title>
</head>
<body>
<button id="button" aria-describedby="tooltip">My button</button>
<div id="tooltip" role="tooltip">My tooltip</div>
<script src="https://unpkg.com/@popperjs/core@2.0.0"></script>
<script>
const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');
Popper.createPopper(button, tooltip);
</script>
</body>
</html>
Styling
Let's give our tooltip some styling:
<!DOCTYPE html>
<html>
<head>
<title>Popper Tutorial</title>
<style>
#tooltip {
background: #333;
color: white;
font-weight: bold;
padding: 4px 8px;
font-size: 13px;
border-radius: 4px;
}
</style>
</head>
<body>
<!-- ... -->
</body>
</html>
Here's the result so far:
Arrow
Our tooltip is currently just a box. In many UI design systems, this is all tooltips need, but others prefer to have an arrow that points toward the reference element.
Add an arrow element with a data-popper-arrow
attribute like so:
<div id="tooltip" role="tooltip">
My tooltip
<div id="arrow" data-popper-arrow></div>
</div>
Now for styling:
#arrow,
#arrow::before {
position: absolute;
width: 8px;
height: 8px;
z-index: -1;
}
#arrow::before {
content: '';
transform: rotate(45deg);
background: #333;
}
The ::before
pseudo-element is required because Popper uses transform
to
position the arrow inside the popper, but we want to use our own transform
to
rotate the arrow box into a diamond.
Now we need to offset the arrow depending on the popper's current placement.
Popper provides this information with the data-popper-placement
attribute:
#tooltip[data-popper-placement^='top'] > #arrow {
bottom: -4px;
}
#tooltip[data-popper-placement^='bottom'] > #arrow {
top: -4px;
}
#tooltip[data-popper-placement^='left'] > #arrow {
right: -4px;
}
#tooltip[data-popper-placement^='right'] > #arrow {
left: -4px;
}
Why the
^=
? This means "starts with", because we can also have variation placements liketop-start
.
Here's the result so far:
Offset
Our arrow currently overlaps the reference, we can give it a distance of 8px
using the offset
modifier:
Popper.createPopper(button, tooltip, {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8],
},
},
],
});
Here's the result so far:
Functionality
We only want our tooltip to show when hovering or focusing the button.
#tooltip {
/* ... */
display: none;
}
#tooltip[data-show] {
display: block;
}
function show() {
tooltip.setAttribute('data-show', '');
}
function hide() {
tooltip.removeAttribute('data-show');
}
const showEvents = ['mouseenter', 'focus'];
const hideEvents = ['mouseleave', 'blur'];
showEvents.forEach(event => {
button.addEventListener(event, show);
});
hideEvents.forEach(event => {
button.addEventListener(event, hide);
});
Here's the final result:
Performance
Once we create a popper with createPopper
, it stays "alive". This means while
scrolling the page, the popper is constantly being updated, even if it is not
visible. We need to create and destroy the popper instance when necessary:
let popperInstance = null;
function create() {
popperInstance = Popper.createPopper(button, tooltip, {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8],
},
},
],
});
}
function destroy() {
if (popperInstance) {
popperInstance.destroy();
popperInstance = null;
}
}
function show() {
tooltip.setAttribute('data-show', '');
create();
}
function hide() {
tooltip.removeAttribute('data-show');
destroy();
}
Full code
<!DOCTYPE html>
<html>
<head>
<title>Popper Tutorial</title>
<style>
#tooltip {
background: #333;
color: white;
font-weight: bold;
padding: 4px 8px;
font-size: 13px;
border-radius: 4px;
display: none;
}
#tooltip[data-show] {
display: block;
}
#arrow,
#arrow::before {
position: absolute;
width: 8px;
height: 8px;
z-index: -1;
}
#arrow::before {
content: '';
transform: rotate(45deg);
background: #333;
}
#tooltip[data-popper-placement^='top'] > #arrow {
bottom: -4px;
}
#tooltip[data-popper-placement^='bottom'] > #arrow {
top: -4px;
}
#tooltip[data-popper-placement^='left'] > #arrow {
right: -4px;
}
#tooltip[data-popper-placement^='right'] > #arrow {
left: -4px;
}
</style>
</head>
<body>
<button id="button" aria-describedby="tooltip">My button</button>
<div id="tooltip" role="tooltip">
My tooltip
<div id="arrow" data-popper-arrow></div>
</div>
<script src="https://unpkg.com/@popperjs/core@2.0.0"></script>
<script>
const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');
let popperInstance = null;
function create() {
popperInstance = Popper.createPopper(button, tooltip, {
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8],
},
},
],
});
}
function destroy() {
if (popperInstance) {
popperInstance.destroy();
popperInstance = null;
}
}
function show() {
tooltip.setAttribute('data-show', '');
create();
}
function hide() {
tooltip.removeAttribute('data-show');
destroy();
}
const showEvents = ['mouseenter', 'focus'];
const hideEvents = ['mouseleave', 'blur'];
showEvents.forEach(event => {
button.addEventListener(event, show);
});
hideEvents.forEach(event => {
button.addEventListener(event, hide);
});
</script>
</body>
</html>
Finished
You've now created a basic tooltip using Popper! Of course, there is more you can do, such as adding animations or interactivity. These are up to you to explore when creating abstractions for common popper elements like tooltips, popovers, drop-downs, and more.