CSS Anchor Positioning for Advanced Popover Systems

Peter BamigboyePeter Bamigboye
8 min read

Building Modern UI Components Without JavaScript Gymnastics

As front-end developers, we've all been there: trying to position tooltips, popovers, and dropdown menus that need to appear relative to a specific element on the page. For years, our options were limited, either perform HTML/CSS gymnastics or resort to JavaScript for precise positioning. The result? Complex, hard-to-maintain code that often breaks accessibility.

But now, we have CSS Anchor Positioning, a powerful native solution that works beautifully with the new Popover API to create truly sophisticated UI components with minimal code.

Why Anchor Positioning Matters

Before anchor positioning, associating an element with another element and dynamically changing its location based on an anchor's position required JavaScript, which added complexity and performance issues. It also wasn't guaranteed to work in all situations. This was particularly frustrating when building components that needed to adapt to scrolling, resizing, or other layout shifts.

As a developer who's built countless tooltips and dropdown menus over the years, I can tell you firsthand that the fragility of JavaScript positioning solutions has been a constant source of headaches. Elements appearing offscreen, flickering during position calculations, or breaking on mobile were all common issues.

The Power of Anchor Positioning + Popover API

The real magic happens when you combine these two modern web APIs:

  1. CSS Anchor Positioning: Positions elements relative to another element (anchor)

  2. Popover API: Handles UI states, accessibility, focus management, and layer stacking

When building components where content needs to become visible upon user interaction, it can be a challenge to ensure they are fully accessible to users of assistive technologies, and often require additional JS to get this right. Using web platform features like the Popover API can help us build more accessible websites, as much of the necessary functionality comes already baked in.

Core Concepts of Anchor Positioning

Let's break down the key concepts you need to understand:

1. Explicit Anchor Association

To associate an anchor with a positioned element, you need two primary properties:

/* Designate the button as an anchor */
.trigger-button {
  anchor-name: --tooltip-anchor;
}

/* Position the tooltip relative to this anchor */
.tooltip {
  position: absolute; /* or fixed */
  position-anchor: --tooltip-anchor;
}

2. Positioning with Anchor Functions

CSS anchor positioning enables anchor-positioned elements to be placed relative to the edges of their associated anchor(s). The module defines the anchor() function, which is a valid value for each of the inset properties.

Let's say you want a tooltip to appear above a button:

.tooltip {
  position: absolute;
  position-anchor: --tooltip-anchor;
  bottom: anchor(top);
  left: anchor(center);
  translate: -50% -10px; /* Center horizontally and offset from button */
}

This sets the tooltip's bottom edge to align with the button's top edge, and centers it horizontally.

3. Implicit Anchor Association with Popovers

Associating any kind of popover with its invoker creates an implicit anchor reference between the two. This causes the invoker to become the popover's anchor element, meaning that you can position the popover relative to it using CSS anchor positioning.

<button popovertarget="my-popover">Open Menu</button>
<div id="my-popover" popover>Popover content</div>

With the above markup, the popover already has an implicit reference to the button as its anchor, even without using anchor-name or position-anchor CSS properties.

Building a Real-World Advanced Dropdown Menu

Let's create a multi-level dropdown navigation menu that:

  1. Opens on click

  2. Positions submenus intelligently

  3. Animates smoothly

  4. Works without JavaScript

  5. Is fully accessible

HTML Structure Breakdown

  1. Main Navigation Container:

    • The <nav class="main-nav"> serves as the wrapper for the entire navigation menu.

    • This contains the top-level unordered list that houses all menu items.

  2. Top-Level Menu Button:

    • <button popovertarget="dropdown-1">Products</button> creates a button that when clicked will show/hide the dropdown menu.

    • The popovertarget attribute connects this button to the dropdown element with the ID "dropdown-1".

  3. Primary Dropdown Container:

    • <div popover id="dropdown-1" class="dropdown"> creates the first-level dropdown.

    • The popover attribute makes this element a popover that can be shown/hidden by its associated button.

    • The id="dropdown-1" matches the popovertarget value from the button, establishing their connection.

  4. Nested Submenu Structure:

    • Inside the first dropdown, there's another button with popovertarget="subdropdown-1".

    • This button is connected to another popover with id="subdropdown-1".

    • This creates a nested submenu that appears when hovering over or clicking "Product B".

CSS Explanation

Popover Base Styling

[popover] {
    position: fixed;
    min-width: 180px;
    margin: 0;
}
  • The [popover] attribute selector targets all elements with the popover attribute

  • position: fixed is essential for anchor positioning to work - without this, the positioning functions would have no effect

  • Setting margin: 0 removes the default spacing that browsers apply to popovers

Anchor Positioning for First-Level Dropdown

#dropdown-1 {
    top: anchor(bottom);
    left: anchor(start);
    margin-top: 0.5rem;
}
  • top: anchor(bottom) positions the dropdown's top edge at the bottom edge of its implicit anchor (the button that triggered it)

  • left: anchor(start) aligns the left edge of the dropdown with the start edge of the button

  • The margin-top: 0.5rem creates visual spacing between the dropdown and its trigger

Intelligent Subdropdown Positioning

.subdropdown {
    top: anchor(top);
    left: anchor(end);
    margin-left: 0.5rem;
}
  • top: anchor(top) aligns the top of the submenu with the top of its parent menu item

  • left: anchor(end) positions the submenu to start at the right edge of its parent item

  • margin-left: 0.5rem creates spacing between the parent menu and submenu

Popover Animation System

[popover] {
    display: none;
    opacity: 0;
    transform: translateY(5px);
    transition:
        opacity 200ms ease,
        transform 200ms ease,
        display 200ms allow-discrete,
        overlay 200ms allow-discrete;
}
  • The transition property creates smooth animations for both opacity and transform changes

  • display 200ms allow-discrete is critical - it enables transitions for the display property which normally cannot be animated

  • overlay 200ms allow-discrete ensures the popover stays in the top layer during animation

  • transform: translateY(5px) creates a starting position slightly below where the dropdown will end up

[popover]:popover-open {
    display: block;
    opacity: 1;
    transform: translateY(0);

    @starting-style {
        opacity: 0;
        transform: translateY(5px);
    }
}
  • The :popover-open pseudo-class targets the popover when it's visible

  • @starting-style is a new CSS feature that sets the initial values for the opening animation

  • Without @starting-style, the popover would appear immediately and only animate when closing

Dropdown Arrow Indicator

#dropdown-1::before {
    content: "";
    position: absolute;
    top: -6px;
    left: 1rem;
    width: 12px;
    height: 12px;
    background: var(--menu-background);
    transform: rotate(45deg);
    border-left: 1px solid var(--menu-border);
    border-top: 1px solid var(--menu-border);
}
  • Creates a small rotated square that appears as a triangular arrow pointing up toward the trigger button

  • Positioned just above the dropdown to create a visual connection between the dropdown and its trigger

  • The borders match the dropdown border to create a seamless visual connection

Popover Trigger Indicators

.main-nav button[popovertarget]::after {
    content: "";
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 5px solid currentColor;
    margin-left: 0.5rem;
}

.main-nav li button[popovertarget="subdropdown-1"]::after {
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    border-left: 4px solid currentColor;
    border-right: none;
}
  • Uses the attribute selector [popovertarget] to target only buttons that control popovers

  • Creates different arrow indicators: downward for top-level and rightward for nested dropdowns

  • These provide a visual cue that clicking will reveal additional content

Fallback Positioning for Smart Edge Detection

One of the most powerful features of anchor positioning is fallback positioning to handle edge cases, like when a dropdown would render offscreen.

CSS anchor positioning also provides CSS-only mechanisms for specifying multiple alternative positions for an anchor-positioned element. For example, if a tooltip is anchored to a form field but the tooltip would otherwise be rendered offscreen in its default position settings, the browser can try rendering it in a different suggested position so it is placed onscreen.

Here's a practical implementation:

Real-World Considerations and Best Practices

Progressive Enhancement

Always build with fallbacks in mind. Test your components with anchor positioning disabled to ensure they remain functional. A good approach is to use feature detection:

if (CSS.supports('anchor-name: --anchor')) {
  // Use anchor positioning
} else {
  // Use JavaScript positioning fallback
}

Animation Techniques

The combination of three new CSS features enables smooth animations without JavaScript:

  1. transition-behavior: allow-discrete makes the display property animatable

  2. @starting-style defines initial animation states for elements that are appearing

  3. Include overlay in your transitions to keep popovers in the top layer during animations

[popover] {
  transition: opacity 200ms, display 200ms allow-discrete;
}

[popover]:popover-open {
  @starting-style { opacity: 0; }
}

Troubleshooting Checklist

If your anchor positioning isn't working:

  1. Verify the anchor element is visible (hidden elements can't be anchors)

  2. Confirm you're using position: absolute or position: fixed

  3. Check for typos in your anchor name values

  4. Remember that popovers automatically create implicit anchor references

Browser Support Strategy

As of mid-2025, implement a layered approach:

  1. Use feature detection rather than browser detection

  2. Provide JavaScript positioning fallbacks for critical components

  3. Consider lightweight polyfills for consistent behavior

  4. Monitor caniuse.com for support updates

Conclusion

CSS Anchor Positioning fundamentally transforms how we build interactive UI components on the web. By eliminating complex JavaScript positioning logic and replacing it with declarative CSS, it delivers three key benefits:

  1. Simpler codebase: Replace hundreds of lines of positioning JavaScript with a few lines of CSS

  2. Better accessibility: The Popover API handles focus management, keyboard interaction, and proper ARIA attributes automatically

  3. Superior performance: Native browser implementations are inherently more efficient than JavaScript calculations

As you implement these features in your projects, remember that the web is fundamentally progressive. Start with solid, accessible foundations and layer on these enhancements where supported.

0
Subscribe to my newsletter

Read articles from Peter Bamigboye directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Peter Bamigboye
Peter Bamigboye

I am Peter, a front-end web developer who writes on current technologies that I'm learning or technologies that I feel the need to simplify.