How Astryx Popover and useLayer Hooks Enable Precise Overlay Positioning

Astryx Popover is a React wrapper around the native <dialog> Popover API that uses CSS anchor positioning and the useLayer hook to render floating panels above other content while automatically tracking anchor element changes.

The facebook/astryx repository provides a modern overlay system that combines native browser APIs with React hooks. Understanding how the Astryx Popover and useLayer hooks work for overlay positioning reveals a performant approach to building tooltips, menus, and dropdowns that stay synchronized with their trigger elements.

Anchor-Based Positioning with CSS Anchor Positioning

The Popover component establishes a relationship between a trigger element and its floating panel through an anchor ref. You pass an anchorRef to the Popover, which the component uses to compute placement relative to the trigger.

Internally, Astryx leverages the browser's CSS anchor positioning properties—position-anchor, position-anchor-size, and position-anchor-gap—to handle calculations. This native CSS approach means the overlay automatically tracks size and position changes of the anchor without requiring JavaScript resize observers or expensive re-calculations. According to the source code in packages/core/src/Tooltip/Tooltip.tsx at line 161, the Tooltip component (which utilizes Popover) relies on these CSS anchor positioning properties to maintain alignment.

Native Popover API Integration

Rather than implementing custom show/hide logic, the Popover component calls native browser methods directly. The hook invokes element.showPopover() to display the overlay and element.hidePopover() to dismiss it, targeting either the anchor element or the panel element itself.

This native integration provides light dismiss functionality automatically—users can close the overlay by clicking outside or pressing the Escape key without additional event listeners. The component also adds appropriate ARIA attributes such as aria-haspopup="true" and role="dialog" when applicable, as documented in packages/lab/src/Tour/TourStep.tsx at lines 216-218.

In the test suite, these native calls are mocked because JSDOM does not implement the Popover API. The mock implementation in packages/core/src/Tooltip/Tooltip.test.tsx (lines 22-27) replaces HTMLElement.prototype.showPopover and hidePopover to enable reliable unit testing.

Layer Promotion via the useLayer Hook

To ensure overlays render above other content—even when anchors live inside scrolling containers or nested component trees—Astryx provides the useLayer hook. This hook moves the Popover's panel into a top-layer portal, creating a dedicated DOM node with a high z-index that exists outside the normal stacking context.

The hook also maintains a layer stack that tracks multiple simultaneous overlays. This prevents visual conflicts when multiple popovers, tooltips, or menus are active concurrently. You can see this implementation in packages/core/src/TopNav/TopNavMenu.tsx at lines 27-34, where usePopover internally calls useLayer to promote the dropdown menu to the top layer.

Accessibility and ARIA Management

The Popover component handles accessibility concerns automatically through its native API integration. When appropriate, it applies aria-haspopup="true" to anchor elements and assigns role="dialog" to the overlay panel.

Because the component uses the browser's native Popover API, it inherits built-in accessibility behaviors including focus management and light dismiss. This eliminates the need for additional boilerplate code to handle keyboard navigation or click-outside detection, keeping the markup clean while maintaining WCAG compliance.

Implementation Examples

Basic Popover Usage

Here is a minimal implementation attaching a Popover to a button element:

import {Popover} from '@astryxdesign/core/Popover';
import {useRef} from 'react';

function Example() {
  const buttonRef = useRef<HTMLButtonElement>(null);

  return (
    <>
      <Popover
        anchorRef={buttonRef}
        placement="bottom"
      >
        <div>Hello world</div>
      </Popover>
      <button ref={buttonRef}>Hover me</button>
    </>
  );
}

Underlying usePopover Hook Structure

The usePopover hook in packages/core/src/Popover/usePopover.ts demonstrates the internal mechanics:

export function usePopover({anchorRef, placement, onShow, onHide}) {
  const layer = useLayer();
  const open = () => {
    const el = anchorRef.current;
    if (el?.showPopover) el.showPopover();
    onShow?.();
  };
  const close = () => {
    const el = anchorRef.current;
    if (el?.hidePopover) el.hidePopover();
    onHide?.();
  };
  return {open, close, layer};
}

Complex Component Implementation

For a real-world example, the Chat emoji picker in packages/lab/src/Chat/ChatEmojiPicker.tsx demonstrates Popover usage within a complex UI:

import {Popover} from '@astryxdesign/core/Popover';
import {useRef} from 'react';

export function ChatEmojiPicker() {
  const triggerRef = useRef<HTMLButtonElement>(null);
  return (
    <>
      <Popover anchorRef={triggerRef} placement="top">
        <EmojiGrid />
      </Popover>
      <button ref={triggerRef}>😀</button>
    </>
  );
}

Summary

  • CSS anchor positioning handles overlay placement natively, eliminating JavaScript layout calculations for better performance.
  • The useLayer hook creates a top-layer portal with high z-index and manages stacking for multiple simultaneous overlays.
  • Native Popover API methods (showPopover/hidePopover) provide built-in light dismiss and accessibility features.
  • The anchorRef contract connects trigger elements to their panels, with automatic synchronization of size and position changes.
  • Testing requires mocking the Popover API in JSDOM environments, as demonstrated in the Tooltip test suite.

Frequently Asked Questions

What is the relationship between Popover and useLayer in Astryx?

The Popover component uses the useLayer hook internally to manage DOM placement and z-index stacking. While Popover handles the anchor positioning logic and native API calls, useLayer creates the top-layer portal that ensures the overlay renders above other content regardless of parent container stacking contexts.

How does Astryx Popover handle positioning without JavaScript calculations?

Astryx delegates positioning to the browser's CSS anchor positioning specification. By setting CSS properties like position-anchor and position-anchor-gap on the overlay element, the browser automatically calculates and updates the position relative to the anchor element. This approach, visible in packages/core/src/Tooltip/Tooltip.tsx, avoids the performance costs of JavaScript-based positioning libraries.

Why does Astryx use the native Popover API instead of a custom implementation?

The native Popover API provides light dismiss (click-outside and Escape key handling), proper focus management, and accessibility features without additional code. Using showPopover() and hidePopover() reduces bundle size and ensures consistent behavior across browsers that support the API, while the useLayer hook handles the rendering layer for older browsers or complex stacking scenarios.

How do you test components that use Astryx Popover in JSDOM?

Since JSDOM does not implement the Popover API, you must mock the native methods on HTMLElement.prototype. The test suite in packages/core/src/Tooltip/Tooltip.test.tsx (lines 22-27) provides a reference implementation that mocks showPopover and hidePopover to verify component behavior without requiring a real browser environment.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →