EXEPERTAI LAB

Research alpha

Time Machine
EXEPERTAI LAB
GALLERY / COLLECTION
← Browse Astryx gallery
Popover·hook·@astryxdesign/core

usePopover

Headless hook for click-triggered popovers with focus trapping. Combines useLayer with useFocusTrap, auto-focus, light dismiss, Escape handling, and an optional hidden close button for accessible dialog-like popover behavior. Every painted surface emits the canonical popover target and deprecated popover-surface compatibility alias. A custom composition needing a distinct stable seam should pass and document its own surfaceTarget.

Open in Playground @astryxdesign/core/Popover

Signature

Call shape
usePopover(options: UsePopoverOptions = {}): UsePopoverReturn
Parameters
NameType and behavior
onShow() => void
Callback fired when the popover becomes visible.
onHide() => void
Callback fired when the popover is hidden. Use this to return focus to the trigger when needed.
xstyleStyleXStyles
StyleX styles applied to the popover content wrapper, after the default surface styles.
hasLightDismissboolean · optional · default true
Whether clicking outside dismisses the popover.
hasEscapeDismissboolean · optional · default true
Whether pressing Escape dismisses the popover. Only takes full effect together with hasLightDismiss: false, since native light dismiss also closes on Escape.
hasAutoFocusboolean · optional · default true
Whether to focus the first genuine content control when opened. Dialogs with none fall back to the labeled surface; the generated close control is excluded from initial focus.
hasCloseButtonboolean · optional · default true
Whether to include a hidden close button that appears for keyboard users.
closeButtonLabelstring · optional · default 'Close popover'
Accessible label for the hidden close button.
dialogLabelstring
Accessible label for the popover dialog (only applies when role is "dialog"). Provide one when there is no visible title.
role'dialog' | 'none' · optional · default 'dialog'
ARIA role on the content wrapper. Use "dialog" for genuine dialog content; use "none" for listbox/menu popups whose own content role should be exposed and whose trigger keeps DOM focus.
isModalboolean · optional · default true
Whether a dialog-role popover is modal (aria-modal). Only applies when role is "dialog".
hasSurfaceboolean · optional · default true
Whether to apply the default popover surface background, radius, and shadow.
surfaceTargetstring
Optional component-owned refinement target on the painted surface, without the astryx- prefix. Use and document one when a direct hook composition needs distinct theme reachability. Do not use popover-surface; it is a deprecated compatibility alias of the canonical popover target.
Returns
NameType and behavior
triggerRef(el: HTMLElement | null) => void
Ref callback to attach to the trigger element for CSS anchor positioning.
contentRefRefObject<HTMLDivElement | null>
Ref for the popover content container used by focus trapping.
anchorIdstring
CSS anchor name for advanced positioning cases.
show(options?: {skipAutoFocus?: boolean}) => void
Imperatively show the popover. skipAutoFocus preserves current focus for input-triggered popovers.
hide() => void
Imperatively hide the popover.
toggle() => void
Toggle the popover open or closed.
isOpenboolean
Whether the popover is currently open.
idstring
Unique ID for aria-describedby or aria-controls.
render(children: ReactNode, props?: ContextRenderProps) => ReactNode
Render function for anchor-positioned popover content. Pass placement and alignment here. Logical: start/end resolve against the popover's own inherited direction (RTL mirrors in pure CSS).
triggerProps{aria-haspopup: "dialog" | "true"; aria-expanded: boolean; aria-controls: string}
ARIA attributes to spread onto the trigger element. aria-haspopup reflects the popover role.

Showcases and examples

2 documented examples

Popover

Popover API entry

Preview loads on approachPreview loads on approach
Exact source · popover-showcase
// Copyright (c) Meta Platforms, Inc. and affiliates.

'use client';

import {useState} from 'react';
import {Popover} from '@astryxdesign/core/Popover';
import {Button} from '@astryxdesign/core/Button';
import {VStack} from '@astryxdesign/core/Layout';
import {Text, Heading} from '@astryxdesign/core/Text';
import {Divider} from '@astryxdesign/core/Divider';

export default function PopoverShowcase() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <Popover
      isOpen={isOpen}
      onOpenChange={setIsOpen}
      hasAutoFocus={false}
      placement="below"
      label="Settings"
      width={280}
      content={
        <VStack gap={3}>
          <Heading level={4} tabIndex={0}>
            Settings
          </Heading>
          <Divider />
          <Text type="body">
            Notifications, dark mode, and sound preferences.
          </Text>
        </VStack>
      }>
      <Button label="Settings">Settings</Button>
    </Popover>
  );
}

Usage

Headless hook for click-triggered popovers with focus trapping. Combines useLayer with useFocusTrap, auto-focus, light dismiss, Escape handling, and an optional hidden close button for accessible dialog-like popover behavior. Every painted surface emits the canonical popover target and deprecated popover-surface compatibility alias. A custom composition needing a distinct stable seam should pass and document its own surfaceTarget.

  • Use for interactive content such as menus, pickers, forms, and command panels that need focus management.
  • Prefer the Popover component for standard trigger-content pairs; use the hook for custom trigger patterns.
  • Use popover as the broad surface target. Popover-surface remains supported compatibility output, but new theme source uses the canonical key.
  • When a custom composition needs its own theme refinement, pass and document an owned surfaceTarget such as selector-popup. It refines the Popover surface rather than creating another anatomy part.
  • Use for non-interactive hover previews: use useHoverCard or useTooltip instead.