DEV Community

Vivian Voss
Vivian Voss

Posted on • Originally published at vivianvoss.net

The Native Popover That Positions Itself

Claudine watches a browser figure out popover positioning on its own, chin on hand, amused.<br>

Stack Patterns — Episode 10

Every frontend team has written this code: a tooltip that appears on click, disappears when you click elsewhere, stays above the z-index of that one modal someone set to 9999, and kindly repositions itself when it hits the viewport edge. The result is typically Floating UI (35KB), three event listeners, a resize observer, and a quiet prayer.

The browser now does all of it. Natively.

Stable: The Popover API

Baseline in every browser since April 2025. One attribute:

<button popovertarget="tip">Settings</button>
<div id="tip" popover>Saved automatically.</div>
Enter fullscreen mode Exit fullscreen mode

That is the entire JavaScript: none. The browser provides the top layer (no z-index required), light dismiss (click outside to close), keyboard handling (ESC), and focus management. For free. One does rather appreciate a specification that ships with fewer bugs than most npm packages.

Style it with :popover-open:

[popover]:popover-open {
  opacity: 1;
  transition: opacity 0.2s;
  transition-behavior: allow-discrete;
}
Enter fullscreen mode Exit fullscreen mode

The pseudo-class tells you the state. The transition tells you the mood. No aria-expanded toggling, no classList.add, no state management library to track whether a rectangle is visible.

Coming: Anchor Positioning

Chrome 125+ and Safari 26+ ship CSS Anchor Positioning. Firefox is behind a flag but closing in. When it lands everywhere, the second half of the problem dissolves:

.trigger { anchor-name: --btn; }

[popover] {
  position-anchor: --btn;
  top: anchor(bottom);
  left: anchor(left);
  position-try-fallbacks: flip-block, flip-inline;
}
Enter fullscreen mode Exit fullscreen mode

position-anchor ties the popover to its trigger. anchor() places it relative to that trigger, anywhere on the page, regardless of DOM hierarchy. And position-try-fallbacks is the part that earns its keep: if the popover does not fit below, flip it above. If not above, try the side. The browser measures, decides, repositions. No scroll listener. No IntersectionObserver. No "getBoundingClientRect and hope for the best."

The Point

Floating UI: 35KB. Popper.js: 28KB. Tippy.js: 22KB. The native equivalent: zero kilobytes and a few lines of CSS. The popover half works today. The positioning half is landing.

One rather suspects the library authors saw this coming.

Read the full article on vivianvoss.net →


By Vivian Voss — System Architect & Software Developer. Follow me on LinkedIn for daily technical writing.

Top comments (0)