Tooltips and Help Bubbles in Plain HTML: Popover Patterns That Actually Hold Up in 2026
Tooltips are one of those interface patterns that seem trivially simple until you actually try to build one that works. A small text bubble next to a button—how hard can it be? Hard enough that most implementations in 2026 are still broken in at least one dimension: invisible to screen readers, unreachable by keyboard, impossible to read on mobile, or positioned off-screen when the trigger element is near a viewport edge. The title attribute, which was supposed to solve this, never did. Tooltip libraries weigh 15–40KB and introduce framework dependencies for what should be a native browser feature. The Popover API, now supported everywhere that matters, finally makes lightweight, accessible tooltips possible in plain HTML. But “possible” is not the same as “automatic.” You still need to wire up the semantics correctly.
This guide covers how to build tooltips and help bubbles using the Popover API, when to use a popover versus the title attribute versus ARIA, how to handle positioning, what mobile touch behavior looks like, and where JavaScript is still genuinely needed.
Why the title Attribute Is Not a Tooltip
The title attribute has been in HTML since the 1990s. Hover over an element with a title and the browser shows a small yellow box with the text. It looks like a tooltip. It is not a usable one.
Problems with title:
- No keyboard access. The tooltip only appears on mouse hover. Keyboard users never see it.
- No touch access. Mobile browsers mostly ignore
titleentirely. On iOS Safari, you can trigger it with a long press, but almost no one knows that. - No styling control. You cannot change the font, colour, width, position, or timing of the browser’s native tooltip rendering. It appears after an OS-determined delay, stays for an OS-determined duration, and disappears.
- Screen reader inconsistency. Some screen readers read
titleas a secondary label, some ignore it, some read it only in certain modes. You cannot rely on it to convey essential information. - Truncation. Long
titletext gets cut off in some browsers without any indication that content is missing.
WCAG 2.2 explicitly warns against using title as the only means of conveying information (Success Criterion 1.3.1). If the tooltip contains something the user needs to know, it must be available through another channel. The title attribute is, at best, a supplementary hint for mouse users. It is not an accessible tooltip mechanism.
The Popover API Tooltip: Basic Pattern
The Popover API provides the show/hide mechanics, top-layer rendering, and light-dismiss behaviour that a tooltip needs. Here is the minimal pattern:
<button popovertarget="tip-save" popovertargetaction="toggle" aria-describedby="tip-save">
Save
</button>
<div id="tip-save" popover role="tooltip">
Saves the current document to your local project folder.
</div>
What this gives you:
- Clicking the button toggles the tooltip. Click once to open, click again to close.
- Pressing Escape while the tooltip is open closes it.
- Clicking anywhere else on the page closes it (light dismiss).
- The tooltip renders in the top layer, above all other content—no z-index management.
aria-describedbylinks the button to the tooltip text, so screen readers announce it as a description of the button.role="tooltip"signals to assistive technology that this element is supplementary descriptive text.
No JavaScript. No library. About six lines of HTML.
Styling the Tooltip
The browser’s default popover rendering is a white box centred on the viewport. That is obviously wrong for a tooltip. You need to position it near the trigger and style it to look like a tooltip:
[role="tooltip"][popover] {
position: fixed;
inset: unset;
margin: 0;
padding: 0.4rem 0.75rem;
background: #1a1a2e;
color: #f0f0f0;
font-size: 0.85rem;
line-height: 1.4;
border-radius: 4px;
max-width: 280px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
border: none;
pointer-events: none;
}
The pointer-events: none is deliberate. Tooltips should not intercept mouse clicks. The user should be able to click through a visible tooltip to reach content underneath it. If your tooltip needs to be interactive (containing links or buttons), it is not a tooltip—it is a popover or a disclosure widget, and you should treat it as one.
Positioning: The Real Problem
Getting a tooltip to appear next to its trigger, rather than in the centre of the viewport, is the main challenge. The Popover API handles visibility. It does not handle positioning relative to another element—that is what CSS anchor positioning is for.
In 2026, CSS anchor positioning is supported in Chrome, Edge, and Firefox. Safari support has landed in Safari 18. The combination of the Popover API and anchor positioning is the first fully browser-native tooltip solution that does not require JavaScript for positioning.
#save-btn {
anchor-name: --save-btn;
}
#tip-save {
position: fixed;
position-anchor: --save-btn;
bottom: anchor(top);
left: anchor(center);
translate: -50% 0;
margin-bottom: 0.5rem;
}
This positions the tooltip above the button, centred horizontally. The anchor() function references the trigger element’s edges. anchor(top) is the top edge of the anchor. anchor(center) is the horizontal centre. The translate: -50% 0 offsets the tooltip by half its own width to centre it properly.
Fallback Positioning
If you need to support older browsers that lack anchor positioning, the fallback is manual positioning with CSS. Wrap the trigger and tooltip in a relative container:
<span class="tooltip-wrapper">
<button popovertarget="tip-save" aria-describedby="tip-save">Save</button>
<div id="tip-save" popover role="tooltip">
Saves the current document to your local project folder.
</div>
</span>
.tooltip-wrapper {
position: relative;
display: inline-block;
}
.tooltip-wrapper [role="tooltip"][popover] {
position: absolute;
bottom: calc(100% + 0.5rem);
left: 50%;
translate: -50% 0;
}
This works because position: absolute on the popover positions it relative to the wrapper. The limitation is that the tooltip can be clipped by ancestor elements with overflow: hidden. The anchor positioning approach avoids this because position: fixed elements in the top layer are not clipped by overflow. Use the wrapper approach as a fallback, and the anchor approach as the primary method.
Help Bubbles: A Different Pattern
Tooltips are brief, supplementary, and non-essential. Help bubbles—the contextual help panels that appear next to form fields, settings, or interface elements—are different. They contain substantive information the user may genuinely need, they should be persistent until dismissed, and they often contain structured content (paragraphs, lists, links).
The Popover API handles both, but the ARIA semantics differ:
<label for="api-key">API Key</label>
<input type="text" id="api-key" aria-describedby="help-api-key">
<button
popovertarget="help-api-key"
aria-expanded="false"
aria-label="Help for API key field"
>
?
</button>
<div id="help-api-key" popover>
<p>Your API key is a 32-character string found in your account dashboard under Settings → API Access.</p>
<p>If you have not generated a key yet, visit your dashboard first. Keys expire after 90 days.</p>
</div>
Differences from the tooltip pattern:
- No
role="tooltip". This is not supplementary text—it is a help panel. The generic popover role is appropriate. aria-expandedon the trigger button, toggled in sync with the popover state. This tells screen readers whether the help panel is currently visible.aria-labelon the button because ”?” is not a descriptive label. Without it, screen readers announce “button, question mark,” which is meaningless.pointer-eventsis not set tonone. The help bubble may contain selectable text or links.
Synchronise aria-expanded with a tiny script:
document.querySelectorAll('[popovertarget]').forEach(trigger => {
const target = document.getElementById(trigger.getAttribute('popovertarget'));
if (!target || !trigger.hasAttribute('aria-expanded')) return;
target.addEventListener('toggle', (e) => {
trigger.setAttribute('aria-expanded', e.newState === 'open');
});
});
This is the same pattern used for accessible dropdown menus—the toggle event is the universal integration point between the Popover API and your ARIA state management.
When to Use Each Pattern
Choosing between title, a tooltip popover, a help bubble, and an ARIA-only description depends on what the content is and who needs it:
Use title when the text is purely supplementary and losing it changes nothing. Icon buttons that already have visible labels. Abbreviation expansions. Content that is nice to have but not necessary for understanding the interface. Accept that keyboard and mobile users will not see it.
Use a tooltip popover (role="tooltip") when the text provides useful context that should be available to all users, but is not essential for operating the interface. Short descriptions of toolbar buttons. Brief explanations of a setting’s effect. The user should be able to discover the information easily but does not need it to complete their task.
Use a help bubble (popover without role="tooltip") when the content is substantive, potentially multi-paragraph, or contains links or structured content. Form field instructions. Feature explanations. Contextual documentation. The user may need this information to proceed.
Use aria-describedby pointing to visible text when the description should always be visible, not hidden behind an interaction. Error messages. Required field indicators. Input format hints (like “DD/MM/YYYY”). If the text should be visible to sighted users, do not hide it in a tooltip.
Mobile Touch Behaviour
Tooltips and touch screens have a fundamental conflict. Tooltips traditionally appear on hover, but touch devices do not have a hover state. The Popover API resolves this by making the interaction click/tap-based rather than hover-based. Tap the trigger, the tooltip appears. Tap elsewhere, it dismisses. This works naturally on mobile without any special handling.
However, there are edge cases:
Small tap targets. A small ”?” help button that is perfectly clickable with a mouse cursor may be too small for a finger. WCAG 2.2 requires a minimum target size of 24×24 CSS pixels (Success Criterion 2.5.8). For help bubble triggers, 32×32 pixels is a comfortable minimum.
Tooltip obscuring the trigger. On small screens, a tooltip positioned above a button may cover the button itself. Light dismiss (tapping elsewhere) solves this, but the user may not know that. Position tooltips to the side on narrow viewports, or ensure the tooltip does not overlap its own trigger.
Long-press conflicts. On some mobile browsers, long-pressing a button triggers a context menu or text selection. This does not usually affect popover triggers, but test it on real devices if your help trigger uses a non-standard element.
When JavaScript Is Still Needed
The Popover API with CSS anchor positioning handles the majority of tooltip use cases without JavaScript. But there are specific scenarios where a script is still required:
Hover-to-show on desktop. If you want the tooltip to appear on hover (for mouse users) and on click/tap (for keyboard and touch users), you need JavaScript to call showPopover() on mouseenter and hidePopover() on mouseleave. The Popover API’s built-in toggle is click-only.
document.querySelectorAll('.hover-tooltip-trigger').forEach(trigger => {
const target = document.getElementById(trigger.getAttribute('popovertarget'));
if (!target) return;
trigger.addEventListener('mouseenter', () => target.showPopover());
trigger.addEventListener('mouseleave', () => target.hidePopover());
// Focus support for keyboard users
trigger.addEventListener('focusin', () => target.showPopover());
trigger.addEventListener('focusout', () => target.hidePopover());
});
This adds hover and focus behaviour while preserving the click-based toggle as a fallback. The toggle event still fires correctly for ARIA synchronisation.
Dynamic positioning based on available space. CSS anchor positioning with position-try-fallbacks handles basic overflow cases (flipping a tooltip from above to below when it would go off-screen). But if you need complex repositioning logic—shifting horizontally, resizing, or moving to a completely different position—you may need JavaScript. In practice, the CSS fallback system handles most real-world cases. Only reach for JavaScript positioning if the CSS approach demonstrably fails for your layout.
Delayed show/hide. If you want a tooltip to appear after a 300ms delay (to prevent flicker when the mouse passes over triggers quickly) or disappear after a delay (to let the user move their mouse into the tooltip for interactive content), that timing requires JavaScript. CSS does not have a mechanism to delay popover show/hide. A setTimeout wrapper around showPopover() and a clearTimeout on mouseleave is all you need—five lines of code.
Building Tooltips in a DFM2HTML Workflow
DFM2HTML exports clean HTML and CSS, which makes adding tooltip patterns straightforward. Design your page layout in DFM2HTML—including the elements that will have tooltips—then export the site. Add the popover attributes (popover, popovertarget, role="tooltip", aria-describedby) to the relevant elements in the exported HTML, add the tooltip CSS to your stylesheet, and optionally include a small script for hover behaviour or aria-expanded synchronisation.
DFM2HTML’s JavaScript menu system already handles interactive navigation patterns. Tooltips for menu items—showing a brief description when a user hovers or focuses a navigation link—can integrate alongside that existing script. The features page details the JS components DFM2HTML includes, so you can see which interactive patterns are already covered before writing custom tooltip code.
Accessibility Checklist for Tooltips
Before shipping a tooltip implementation, verify these requirements:
Keyboard reachable. The tooltip trigger must be focusable (use a <button>, not a <span>). The tooltip must appear on focus, not only on hover. If the tooltip only appears on hover, keyboard users cannot access it—a WCAG 2.1.1 failure.
Dismiss with Escape. The Popover API handles this automatically. If you built a custom tooltip without the API, add an Escape keydown handler. WCAG 1.4.13 requires that additional content appearing on hover or focus be dismissible without moving focus.
Persistent while needed. The tooltip must remain visible while the user hovers over it or while the trigger has focus. It must not vanish after a timeout while the user is still reading it. WCAG 1.4.13 again.
No essential content hidden. If the tooltip contains information required to use the interface—error messages, required format specifications, critical instructions—that content must also be available through a persistent, visible mechanism. Tooltips supplement; they do not replace.
Sufficient contrast. The tooltip text must meet WCAG contrast requirements (4.5:1 for normal text, 3:1 for large text). Dark text on a dark background inside a tooltip is a common mistake when the tooltip’s background colour is not explicitly set and inherits from a dark theme.
Minimum target size. The trigger element must be at least 24×24 CSS pixels. Tiny “i” icons or ”?” buttons that are 12px wide fail WCAG 2.5.8.
Where This Fits in a Larger Site
Tooltips and help bubbles are one piece of the UI pattern library that modern static sites need. If you are building navigation, the dropdown menu guide covers the same Popover API foundations applied to navigation structures. The tutorials index links to additional guides on performance, layout, and publishing workflows that complement the interactive patterns covered here.
For DFM2HTML users specifically, the combination of DFM2HTML’s visual page design with hand-coded interactive enhancements is a natural workflow. DFM2HTML handles the layout, typography, and structural HTML. You add popover tooltips, keyboard scripts, and ARIA attributes on top. The template gallery provides starting points with clean navigation structures that are ready for tooltip enhancement.
The Popover API turned tooltips from a library problem into an HTML problem. The positioning is handled by CSS anchor positioning. The accessibility is handled by ARIA attributes and focus management. The only JavaScript you might need is for hover timing and aria-expanded synchronisation—and even that is under 20 lines. In 2026, there is no reason to reach for a tooltip library unless your requirements are genuinely exotic. For the help bubbles, form hints, and interface descriptions that make up 95% of real tooltip use cases, plain HTML is enough.