Guide
Getting started
npm install @devix-labs/popover
import { createTooltip } from '@devix-labs/popover';
import '@devix-labs/popover/styles.css';
createTooltip(document.querySelector('#delete'), { content: 'Removes this row for good' });
<script type="module" src="https://devix.pk/cdn/oss/popover@1.0.0/popover.min.js"></script>
<dx-tooltip content="Removes this row for good"><button>Delete</button></dx-tooltip>
Nothing can cover it or clip it
The panel goes in the browser's top layer, through the native popover API. That means:
- No
z-index. There is not one in the stylesheet. A panel in the top layer is painted above every element on the page, whatever they claim. - No clipping. An anchor inside
overflow: hidden— a table cell, a card, a scrolling sidebar — shows its tooltip in full. - No
appendTo. Tippy's entireappendTooption exists to work around those two problems by moving the element somewhere else in the tree. There is nothing here to move.
There is a test for it: the fixture puts the anchor inside an
overflow: hidden box on a page containing an element at the maximum
z-index, opens the tooltip, and asks the browser what is at the panel's centre.
Tooltips and popovers are different things
import { createTooltip, createPopover } from '@devix-labs/popover';
// Describes its anchor. Hover or focus, an arrow, aria-describedby.
createTooltip(button, { content: 'Removes this row for good' });
// Is a region the anchor opens. Click, focus management, aria-expanded.
createPopover(button, { content: panel, trigger: 'click', focusTrap: true });
A tooltip labels something; a popover is somewhere you go. They get different ARIA, different triggers and different dismissal, so they are two functions rather than one with a flag.
WCAG 2.2 SC 1.4.13
Content on Hover or Focus has three clauses, and almost nothing implements all three. Each one has a test:
Dismissible. Escape closes it without moving the pointer, and without the anchor needing focus.
Hoverable. You can move the pointer onto the panel. Moving diagonally from the anchor crosses the gap between them, and a naive implementation closes under you halfway; here a departure heading towards the panel gets a grace period.
Persistent. It does not close on a timer. It stays until you dismiss it or move away.
And because the rule says hover or focus, a keyboard opens it too: focusing the anchor shows the tooltip, blurring it hides it.
Touch is not hover
A phone has no pointer, so pretending it does is why tooltips on phones open and stick until you tap something unrelated. Here a long press opens a tooltip and the next tap closes it; a popover opens on tap, as it should.
Placement
createTooltip(el, { content: 'Hello', placement: 'right-start' });
Twelve of them: top bottom left right, each with -start and -end.
It flips to the opposite side when there is no room, slides along the edge to
stay on screen, and the arrow follows the anchor rather than the panel — so
after a slide it still points at the thing it belongs to.
An RTL page turns start and end around, because that is what they mean.
Content is text
createTooltip(el, { content: '<b>Careful</b>' }); // shows the tags
createTooltip(el, { content: '<b>Careful</b>', html: true }); // renders them
Most tooltip text is somebody's name or a row from a database, so markup is off until you ask for it.
Pass a Node to put real elements inside, or a function to build the content
freshly each time it opens:
createPopover(el, { trigger: 'click', content: () => renderPreview(row) });