
Getting Started With The Popover API
What happens if you rebuild a single tooltip using the browser’s native model without the aid of a library? The Popover API turns tooltips from something you simulate into something the browser actually understands. Opening and closing, keyboard interaction, Escape handling, and much of the...
Getting Started With The Popover API — Smashing Magazine
Skip to main content Start reading the article Jump to list of all articles Jump to all topics15 min readCSS, HTML, ToolsShare on Twitter, LinkedInAbout The AuthorGodstime Aburu is a front-end developer and writer passionate about building interactive, accessible web experiences. He loves exploring new web APIs, creating … More about Godstime ↬Email NewsletterYour (smashing) email Weekly tips on front-end & UX.Trusted by 182,000+ folks. See User Testing Live Deep Dive On Accessibility Testing with Manuel Matuzović Celebrating 10 million developers Smart Interface Design Patterns, 45 lessons + UX training Advertise on Smashing Magazine Naming Design Systems with Samantha Gordashko Custom Web Forms for Angular, React, & Vue. Your backend.What happens if you rebuild a single tooltip using the browser’s native model without the aid of a library? The Popover API turns tooltips from something you simulate into something the browser actually understands. Opening and closing, keyboard interaction, Escape handling, and much of the accessibility now come from the platform itself, not from ad-hoc JavaScript.Tooltips feel like the smallest UI problem you can have. They’re tiny and usually hidden. When someone asks how to build one, the traditional answer almost always comes back using some JavaScript library. And for a long time, that was the sensible advice.I followed it, too.On the surface, a tooltip is simple. Hover or focus on an element, show a little box with some text, then hide it when the user moves away. But once you ship one to real users, the edges start to show. Keyboard users Tab into the trigger, but never see the tooltip. Screen readers announce it twice, or not at all. The tooltip flickers when you move the mouse too quickly. It overlaps content on smaller screens. Pressing Esc does not close it. Focus gets lost.Over time, my tooltip code grew into something I didn’t really want to own anymore. Event listeners piled up. Hover and focus had to be handled separately. Outside clicks needed special cases. ARIA attributes had to be kept in sync by hand. Every small fix added another layer of logic.Libraries helped, but they were also more like black boxes I worked around instead of fully understanding what was happening behind the scenes.That was what pushed me to look at the newer Popover API. I wanted to see what would happen if I rebuilt a single tooltip using the browser’s native model without the aid of a library.As we start, it’s worth noting that, as with any new feature, there are some things with it that are still being ironed out. That said, it currently enjoys great browser support, although there are several pieces to the overall API that are in flux. It’s worth keeping an eye on Caniuse in the meantime.The “Old” TooltipBefore the Popover API, using a tooltip library was not a shortcut. It was the default. Browsers didn’t have a native concept of a tooltip that worked across mouse, keyboard, and assistive technology. If you cared about correctness, your only option was to use a library, and that is exactly what I did.At a high level, the pattern was always the same: a trigger element, a hidden tooltip element, and JavaScript to coordinate the two.? Helpful text The old approach required ~60 lines of JavaScript with five event listeners and manual state management. The new approach is about 10 lines of declarative HTML with zero event listeners. (Large preview)The library handled the wiring that allowed the element to show on hover or focus, hide on blur or mouse leave, and reposition/resize on scroll.None of it was accidental. It was merely compensating for gaps in web platform features.Why I Used A LibraryThe library was doing real work for me: positioning, flipping at viewport edges, event coordination across input types, and scroll awareness inside complex layouts. Positioning alone justified the dependency. Handling scroll containers, transforms, and responsive layouts correctly is not simple.The real issues showed up in accessibility behavior, not visuals. The tooltip worked, but not all the time. Here’s where things started to fray at the seams:Tooltips sometimes appeared late or not at all.Tabbing quickly could skip them entirely.Escape dismissal was not reliable.Keyboard navigation with the old implementation: Tabbing quickly causes tooltips to be skipped entirely, and Escape dismissal is unreliable.I also ran into issues trying to sync hover and focus behavior:Mouse users expect immediacy.Keyboard users expect predictability.Supporting both meant delays and edge cases.This timing mismatch creates an inconsistent experience across input methods.Not to mention, there were issues with assistive technologies, particularly screen readers: Sometimes the tooltip was announced, sometimes it wasn’t, and sometimes it was announced twice.Screen reader behavior with custom tooltips.Keeping ARIA attributes in sync required manual updates. Miss one state change, and the tooltip became confusing or invisible to the accessibility tree.This Was Not Bad CodeThe implementation was tested, the library was solid, and the behavior was reasonable given the tools available at the time.The core problem was not the code. It was that the web platform lacked proper affordances.For example, the browser has no real way of knowing that the element was a tooltip. Everything was built from conventions: generic elements, event listeners, manually-managed ARIA, and custom dismissal logic.Before: A tangled web of event listeners, state management, and manual ARIA updates. After: The browser understands the relationship declaratively. (Large preview)Over time, the tooltip could become fragile. Small changes carried risk. Minor fixes caused regressions. Worse, adding new tooltips inherited the same complexity. Things technically worked, but never felt settled or complete.That was the state of things when I decided to rebuild the tooltip using the browser’s native Popover API.The Moment I Tried The Popover APII didn’t switch to using the Popover API because I wanted to experiment with something new. I switched because I was tired of maintaining tooltip behavior that I believed the browser should have already understood.I was skeptical at first. Most new web APIs promise simplicity, but still require glue, edge-case handling, or fallback logic that quietly recreates the same complexity that you were trying to escape.So, I tried the Popover API in the smallest way possible. Here’s what that looked like: ?
This button triggers a helpful tip.
The complete tooltip implementation using the Popover APINo event listeners. No state tracking. No ARIA updates handled in JavaScript. I focused the button, and the tooltip appeared. I pressed the Esc key, and it disappeared.What Immediately Stood OutA few things became obvious within minutes:I Didn’t Write Any JavaScript To Open Or Close ItThe browser handled invocation entirely through HTML. The relationship between trigger and tooltip was explicit.The Esc Key Just WorkedI didn’t add a key listener. Pressing the Esc key properly closed the tooltip because the browser understands that popovers should be dismissible.ARIA State Automatically SyncedThe aria-expanded attribute updated on its own when the popover opened and closed. There was no need for manual bookkeeping and no risk of stale state.The browser’s DevTools showing aria-expanded automatically updating from false to true as the popover opens.This was the moment that the Popover API stopped feeling like a convenience and more like true bona fide platform behavior.What surprised me most was not the reduced code but the change in responsibility. Before, the tooltip existed because my JavaScript said so. Now, it exists because the browser understands what it is supposed to be and its role in the markup. The tooltip is no longer simply a box positioned near a button anymore, but participating in the browser’s focus model, the accessibility tree, and native dismissal rules.That’s when my migration to the Popover API started.Understanding Invoker CommandsThe popovertarget and popovertargetaction attributes are part of HTML’s invoker commands, a declarative way to control interactive elements without JavaScript.popovertarget="id": Connects the button to a popover element.popovertargetaction: Specifies what should happen:show: Only opens the popover.hide: Only closes the popover.toggle(default): Opens the popover if closed and closes it if it’s open.This means you can have multiple triggers for the same tooltip: Show Help
Close Help
Help content
The browser coordinates everything with no JavaScript needed for the basic interaction.Free Accessibility WinsThis is the part that made me switch completely. I expected the Popover API to reduce code. I didn’t expect it to remove entire categories of accessibility bugs I had been chasing for years. Before the migration, my tooltip system looked fine at the very least. Keyboard support existed, ARIA attributes were present, and screen readers usually behaved accordingly. But “usually” did a lot of heavy lifting.Once I swapped in native popovers, three things changed immediately.Custom implementations use fragile JavaScript to connect triggers and tooltips. The Popover API creates a native browser connection that assistive technology can trust. (Large preview)1. The Keyboard “Just Works”Keyboard support depended on multiple layers lining up
📰Originally published at smashingmagazine.com
Staff Writer