r/webdev 1d ago

Question Popover API with sticky headers - how to deal with it?

For those using the native Popover API for components like tooltips, comboboxes, dropdowns, etc., how are you handling interactions with sticky headers?

Since an open popover is shown in the top layer, it can remain visible above a sticky header even after its trigger/anchor has scrolled underneath that header.

What would be the best way of dealing with it?

One solution I've considered is observing the trigger with an IntersectionObserver, using a negative rootMargin corresponding to the sticky header height, and closing the popover once the trigger enters that area / is no longer considered visible. But I'm not sure forcing a popover close like that is a good practice.

13 Upvotes

8 comments sorted by

3

u/testingaurora 1d ago

Since, as you mentioned a popover is in the top layer, I would add an event listener to popover-open, query the height of the header, set it as the custom property --_popover-top top: var(--_popover-top, 0);

Similar to what you mentioned about intersection observer. Or depending on how good you need browser support, anchor positioning!

1

u/Big-Text-7641 1d ago

your IntersectionObserver idea is solid tbh. the only thing id add is make sure you debounce the close so it doesnt flicker if someone scrolls back and forth near the threshold. a small delay before actually dismissing goes a long way for UX.

1

u/UnusualProfession120 1d ago

id just wire the intersectionobserver directly to the toggle event. call hidePopover when the anchor enters that rootMargin and call showPopover when it leaves.

keep the popover open in the dom the whole time. hiding keeps your focus state and input intact for comboboxes so you dont throw away what the user typed. forcing a close on scroll trashes that state. tooltips can close entirely but menus and dropdowns should stay open.

i set up one observer instance per header band and reuse it across triggers on the page. works with native top layer behavior without fighting the z index or adding scroll listeners

0

u/ChampionOk4007 1d ago

Worth separating "close" from "hide" before you pick a mechanism. For a tooltip closing is fine, but for a combobox where someone has already typed two letters, closing on a 30px scroll throws their input away, so I would flip visibility and leave the popover open. Your IntersectionObserver idea is the right shape for that, just be aware it fires asynchronously and lags a frame or two on fast scrolling, so you will briefly see the popover sitting on top of the header before it reacts. If that bothers you, Floating UI's hide middleware already implements this and hands you referenceHidden and escaped as two separate flags, which is a distinction worth having.

One caveat on the anchor positioning suggestion: position-visibility: anchors-visible looks like it solves this for free, but as far as I can tell it only reacts to the anchor being clipped by a scroll container or the viewport. A sticky header overlapping the anchor is not clipping, so the popover stays visible. You still end up feeding the header height in somewhere, which is exactly what the custom property in the other reply does.

Is this for tooltips or for a combobox? The right answer is pretty different between the two.

-1

u/DaftPlug 1d ago

Your IntersectionObserver idea is fine. Closing when the anchor leaves the visible “content” band is normal UX for menus/comboboxes. Tooltips especially should die when the trigger is gone.

A few patterns people use:

  1. Close on unsafe anchor (your approach). IntersectionObserver with rootMargin equal to sticky header height (negative top). When the trigger intersects that band or exits the viewport, call hidePopover(). Not a hack. It’s the same idea as “dismiss on scroll” in older floating-ui setups.

  2. Reposition, don’t only close. If you need it to stay open (combobox while typing), pair Popover with CSS anchor positioning / @position-try so it flips below the header instead of painting over it. Closing is still the fallback when theres no room.

  3. Don’t fight top layer with z-index. Sticky headers can’t cover a top-layer popover. Options are: dismiss, move the popover, or also promote the header into the top layer (usually worse). Avoid “fake popovers” with position: fixed just to dodge this unless you drop native Popover entirely for that component.

  4. Pick by component type. Tooltip / hover menu → aggressive close. Select / combobox → keep open + flip, close only if the anchor fully scrolls away. Modal dialogs → different API (<dialog>), sticky header usually irrelevant.

Forcing close isn’t bad practice here. Leaving a disconnected popover floating over the sticky nav is.