Demo
Short Version
Here is the codepen of a tooltip built with Popover and Anchor Positioning that moves around an anchor element when it needs more space. It also has a tether arrow that moves with the popover.
Long Version
The Popover API and CSS Anchor Positioning are a great combo for making a tooltip without any JS required (although we can leverage just a little JS as a progressive enhancement, more below).
There are plenty of examples out there of combining the two to make a tooltip. But I want my tooltip to have a little tether arrow to provide a UX hint as to what the tooltip is anchored to.
Unfortunately when the tooltip moves around (thanks to position-try
) the tether arrow doesn't move with it... there just isn't any way to mess with the pseudo-elements inside of @position-try
.
I asked about this on Mastadon and Una replied that it's a known gap in the API (coming someday hopefully).
Fortunately, Temani responded with an idea to use clip-path
and margin-box
to "hide" the parts of the pseudo-element that we don't want to show. Thankfully the margin
can be modified inside of @position-try
. I modified his example to add a pseudo-element to the right and left sides as well as top and bottom and now we have a tooltip that moves around with a tether arrow! 💪
You can see the full source code and demo on CodePen. Below is a snippet of the CSS that makes it work along with some comments to explain what's going on.
[popover] {
--tether-offset: 1px;
--tether-size: 8px;
position-anchor: --anchor-btn;
position: absolute;
position-area: top;
position-try: --bottom, --left, --right;
/* default tooltip is above anchor so we have a bottom margin */
margin: 0 0 var(--tether-size) 0;
/* in default position (above anchor) the tether arrows on left, top, and right
are hidden because we are clipping based on the margin. */
/* NOTE: the inset here is optional, just a little offest so the arrow
doesn't touch the anchor*/
clip-path: inset(var(--tether-offset)) margin-box;
/* the top and bottom arrows */
&::before {
content: "";
position: absolute;
z-index: -1;
inset: calc(-1 * var(--tether-size)) calc(50% - var(--tether-size));
background: inherit;
clip-path: polygon(
0 var(--tether-size),
50% 0,
100% var(--tether-size),
100% calc(100% - var(--tether-size)),
50% 100%,
0 calc(100% - var(--tether-size))
);
}
/* the left and right arrows */
&::after {
content: "";
position: absolute;
z-index: -1;
inset: calc(50% - var(--tether-size)) calc(-1 * var(--tether-size));
background: inherit;
clip-path: polygon(
0 var(--tether-size),
var(--tether-size) 0,
calc(100% - var(--tether-size)) 0,
100% 50%,
calc(100% - var(--tether-size)) 100%,
var(--tether-size) 100%
);
}
}
Progressive Enhancement (show on hover)
I also like my tooltip to show/hide when hovered by a mouse, so I add a little extra JS for that.
const popover = document.querySelector("[popover]");
const anchor = document.querySelector("button");
// show popover on mouseenter
anchor.addEventListener("mouseenter", () => {
popover.showPopover();
});
// hide popover on mouseleave
anchor.addEventListener("mouseleave", () => {
popover.hidePopover();
});
Hopefully you found this post helpful, if you have any questions you can find me on Twitter.
Or from the RSS feed