Los Angeles AI Apps logo

Lazy-Loading Popovers using Anchor Positioning and Turbo Frames

By Joseph Izaguirre

Image for Lazy-Loading Popovers using Anchor Positioning and Turbo Frames

🔥 If you’d like a custom web application with generative AI integration, visit losangelesaiapps.com

One of my favorite reasons for choosing an HTML-first frontend framework like Turbo or HTMX is that you get to piggy-back off of Web features that just landed in Baseline. You benefit from the work of countless engineers all working to move the Web forward, as opposed to insulating yourself behind a walled garden of JavaScript, never noticing the many new features constantly springing up.

There are two exciting new developments in the Web that I’d like to highlight, and show how they dovetail very nicely with Turbo to unlock a new pattern that wasn’t available before without a 3rd party JavaScript library.

The first is the Popover API, Baseline as of January 27, 2025. This allows you to declaratively create popover content with no JavaScript:

<button popovertarget="mypopover">Toggle the popover</button>
<div id="mypopover" popover>Hello World!</div>

This popover will appear above the other content on the page. The popover attribute comes with an auto state. This gives you a lot of functionality out-of-the-box:

  • “Light dismiss” the popover by clicking outside of it.

  • Dismiss the popover by pressing the ESC key.

  • Opening another popover on the page dismisses the first one.

Of course, since this is the Web, it Just Works across all platforms, including smartphones and tablets.

The Popover API works very well with a new feature that is about to be Baseline: Anchor Positioning. This lets you tether elements together, declaring an “anchor” element and an “anchor-positioned” element that is positioned relative to the anchor:

<!-- The Anchor -->
<div class="anchor">Anchor</div>

<!-- The anchor-positioned element -->
<div class="infobox">
  <p>This is an information box.</p>
</div>
.anchor {
  anchor-name: --myAnchor; /* The Anchor specifies its name */
}

.infobox {
  position: fixed; /* Position must be fixed or absolute */
  position-anchor: --myAnchor; /* Name of anchor */
  position-area: bottom right; /* Position relative to anchor */
}

This reads very nicely, using plain English to declare where you’d like the anchor-positioned element to be relative to the anchor. I’ve barely scratched the surface on Anchor Positioning; it also allows you to gracefully handle overflow if the anchor-positioned element would appear off screen, as well as conditionally hiding the anchor-positioned element.

Put together, Popover and Anchor Positioning put 3rd-party JavaScript libraries like FloatingUI out of business (or at least duplicates a lot of their functionality). Paying down tech debt by leveraging built-in browser behavior is always a smart move!

As of April 2025, Anchor Positioning only works in Chrome and Edge, but it’s on the Interop 2025 roadmap, so expect it to be Baseline in Safari and Firefox sometime this year. Patience!

Turbo: A New Pattern Emerges

So where do Turbo Frames come in? You can add the src and loading=“lazy” attributes to a Turbo Frame to lazily load a section of a page:

<!-- Lazy load Reservation form for Event 4 -->
<turbo-frame id="event_4_reservation" src="/events/4/reservations/new" loading="lazy" class="reservation"> 
</turbo-frame>

When this Turbo frame becomes visible on the page, it will send an HTTP request to /events/4/reservations/new and replace its contents with the corresponding Turbo frame from the server. Let’s make this Turbo frame a popover and add a toggle button:

<button popovertarget="event_4_reservation">RSVP</button>

<!-- Add the popover attribute -->
<turbo-frame id="event_4_reservation" popover src="/events/4/reservations/new" loading="lazy" class="reservation"> 
</turbo-frame>

This keeps the Turbo frame hidden until the button is clicked, saving you an HTTP request until it’s actually needed. Now let’s use CSS Anchor Positioning to position the Turbo frame relative to the toggle button, so that the user “reveals” the Reservation form intuitively once the button is clicked:

.reservation{
    position: absolute;
    position-area: bottom right;

  /* You can, and should, override the default Popover styling here */
}

But wait, don’t we need to specify an Anchor? In this case, no! When using the Popover API to associate a popover element with a control, an implicit anchor association is created. This means that the job of associating the Reservation form with the RSVP button has already been done for us, and we need only to tether them together by specifying the position and position-area CSS attributes. Popover and Anchor Positioning are designed to work together!

Screenshot of a popover anchored to a button

A lazy-loaded Turbo Frame, rendered as a Popover using Anchor Positioning with position-area: bottom right.

This is very helpful in the case where we have multiple Events and controls on the same page: we don’t need to come up with anchor names for each trigger, just use the implicit association:

<button popovertarget="event_4_reservation">RSVP</button>
<turbo-frame id="event_4_reservation" popover src="/events/4/reservations/new" loading="lazy" class="reservation"> </turbo-frame>

<button popovertarget="event_5_reservation">RSVP</button>
<turbo-frame id="event_5_reservation" popover src="/events/5/reservations/new" loading="lazy" class="reservation"> </turbo-frame>

<!-- Events 6, 7, 8, etc -->
/* No need to dynamically set the Anchor via CSS variables, this still works! */
.reservation{
    position: absolute;
    position-area: bottom right;
}

This gets you lazy-loaded popovers, anchored relative to a trigger element, without a single line of custom JavaScript! Just using what comes with Turbo Frames. You can imagine doing something similar with HTMX, if that’s your tool of choice. It’s 2025; time to leverage what the browser gives you for free!