JGuang

Popover

Popover · Astryx

Preview
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Popover

A click-triggered overlay anchored to a button or trigger element. Use it for secondary actions, inline confirmations, or supplementary information that does not warrant a full dialog. For hover previews use HoverCard, for brief helper text use Tooltip.

Installation

npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

Requires React 19+, base CSS and a Theme provider. See Setup.

Popover

A click-triggered overlay anchored to a button or trigger element. Use it for secondary actions, inline confirmations, or supplementary information that does not warrant a full dialog. For hover previews use HoverCard, for brief helper text use Tooltip.

Usage guidance

  • Keep popover content focused on a single task or piece of information.
  • Provide a clear way to close: either by clicking outside or with an explicit close button.
  • Theme the painted surface through the canonical popover target.
  • Avoid: Nest popovers inside other popovers; it creates confusing focus and navigation.
  • Avoid: Assume input complexity alone determines the presentation; evaluate the task's focus, space, and interaction requirements.
  • Avoid: Assume scrolling alone means Popover is the wrong component; a bounded Popover may scroll while a focused anchored interaction remains appropriate.

Flush surface for a list of rows

<Popover
  label="Recent files"
  padding={0}
  content={
    <List>
      <ListItem label="Quarterly report.pdf" />
      <ListItem label="Roadmap.key" />
    </List>
  }>
  <Button label="Recent" variant="secondary" />
</Popover>

Roomier surface for a form

<Popover
  label="Save search"
  padding={4}
  content={<SaveSearchForm />}>
  <Button label="Save search" variant="secondary" />
</Popover>

Theming API

{
  "targets": [
    {
      "className": "astryx-popover"
    }
  ],
  "vars": [
    {
      "name": "--_popover-radius",
      "description": "Border radius of the popover surface",
      "default": "var(--radius-container)",
      "private": true
    }
  ],
  "derived": [
    {
      "property": "borderRadius",
      "vars": [
        "--_popover-radius"
      ]
    }
  ]
}

Popover

A click-triggered popover for displaying interactive content anchored to a trigger element.

PropTypeDefaultDescription
childrenReactNodeTrigger element. Must contain a <button> or [role="button"] element.
anchorRefReact.RefObject<HTMLElement>External ref to use as the popover anchor in sibling mode.
content *ReactNodeContent to display inside the popover.
placement'above' | 'below' | 'start' | 'end''below'Position placement relative to the trigger. Logical: start/end resolve against the popover's own inherited direction, so RTL contexts mirror automatically in pure CSS.
alignment'start' | 'center' | 'end''start'Alignment along the placement axis. Logical: start/end follow the popover's own inherited direction (RTL mirrors).
isOpenbooleanWhether the popover is shown in controlled mode.
onOpenChange(isOpen: boolean) => voidCallback fired when the popover visibility changes.
isEnabledbooleantrueWhen false, trigger interactions are ignored.
widthnumber | string'auto'Width of the popover container. The layer still caps to the viewport with alignment-aware safe-area gutters before scrolling long content.
padding0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 103Inner padding of the popover surface on the spacing scale (0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10), matching the padding prop on Card and Stack. Pass 0 for a flush surface when the content owns its own edges, such as a list of rows or a header with a rule, and pad the content itself. The padding sits on the painted popover target, so a theme padding on that target replaces it.
labelstringAccessible label for the popover dialog.
role'dialog' | 'none''dialog'ARIA role for the popover wrapper. Use dialog for dialog-style popovers; use none when content provides its own role, like menu or listbox.
isModalbooleantrueWhether a dialog-style popover sets aria-modal. Only applies when role is dialog.
hasCloseButtonbooleantrueWhether to include a hidden close button for accessibility.
closeButtonLabelstring'Close popover'Label for the hidden close button.
hasAutoFocusbooleantrueWhether to move focus into the popover when it opens. Focus enters the first genuine content control; dialogs with none fall back to the labeled surface. The generated fallback close control stays hidden until reached through keyboard navigation. Set to false for input-owned focus, inline showcases, or documentation previews.
hasLightDismissbooleantrueWhether clicking outside dismisses the popover. Set to false for surfaces that stay open until explicitly dismissed, like onboarding coachmarks.
hasEscapeDismissbooleantrueWhether pressing Escape dismisses the popover. Only takes full effect together with hasLightDismiss={false}, since native light dismiss also closes on Escape.
xstyleStyleXStylesStyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value, not an inline style object like style={{}}.

Anatomy

  • Trigger element: Caller-supplied or externally referenced control that anchors and toggles the popover.
  • Popover surface: Painted surface owned and themed by Popover through the canonical popover target.
  • Popover content: Caller-supplied content rendered inside the surface.
  • Fallback close control: Keyboard-reachable close affordance appended by usePopover when enabled.

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. A custom composition needing a distinct stable seam should pass and document its own surfaceTarget.

Parameters

ParameterTypeDescription
onShow() => voidCallback fired when the popover becomes visible.
onHide() => voidCallback fired when the popover is hidden. Use this to return focus to the trigger when needed.
xstyleStyleXStylesStyleX styles applied to the popover content wrapper, after the default surface styles.
hasLightDismissbooleanWhether clicking outside dismisses the popover.
hasEscapeDismissbooleanWhether pressing Escape dismisses the popover. Only takes full effect together with hasLightDismiss: false, since native light dismiss also closes on Escape.
hasAutoFocusbooleanWhether 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.
hasCloseButtonbooleanWhether to include a hidden close button that appears for keyboard users.
closeButtonLabelstringAccessible label for the hidden close button.
dialogLabelstringAccessible label for the popover dialog (only applies when role is "dialog"). Provide one when there is no visible title.
role'dialog' | 'none'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.
isModalbooleanWhether a dialog-role popover is modal (aria-modal). Only applies when role is "dialog".
hasSurfacebooleanWhether to apply the default popover surface background, radius, and shadow.
padding0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10Inner padding of the painted surface on the spacing scale (0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10). Omit it and the hook paints no padding of its own; Popover passes 3 by default and 0 for a flush surface.
surfaceTargetstringOptional 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.

Returns

[
  {
    "name": "triggerRef",
    "type": "(el: HTMLElement | null) => void",
    "description": "Ref callback to attach to the trigger element for CSS anchor positioning."
  },
  {
    "name": "contentRef",
    "type": "RefObject<HTMLDivElement | null>",
    "description": "Ref for the popover content container used by focus trapping."
  },
  {
    "name": "anchorId",
    "type": "string",
    "description": "CSS anchor name for advanced positioning cases."
  },
  {
    "name": "show",
    "type": "(options?: {skipAutoFocus?: boolean}) => void",
    "description": "Imperatively show the popover. skipAutoFocus preserves current focus for input-triggered popovers."
  },
  {
    "name": "hide",
    "type": "() => void",
    "description": "Imperatively hide the popover."
  },
  {
    "name": "toggle",
    "type": "() => void",
    "description": "Toggle the popover open or closed."
  },
  {
    "name": "isOpen",
    "type": "boolean",
    "description": "Whether the popover is currently open."
  },
  {
    "name": "id",
    "type": "string",
    "description": "Unique ID for aria-describedby or aria-controls."
  },
  {
    "name": "render",
    "type": "(children: ReactNode, props?: ContextRenderProps) => ReactNode",
    "description": "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)."
  },
  {
    "name": "triggerProps",
    "type": "{aria-haspopup: \"dialog\" | \"true\"; aria-expanded: boolean; aria-controls: string}",
    "description": "ARIA attributes to spread onto the trigger element. aria-haspopup reflects the popover role."
  }
]

Usage guidance

  • 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 canonical surface target.
  • 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.
  • Avoid: Use for non-interactive hover previews: use useHoverCard or useTooltip instead.

Examples

  • Popover:
  • Popover — Bottom Sheet Alternative: Opt-in BottomSheet alternative for compact touch surfaces where actions should use a modal presentation anchored to the bottom edge.
  • Popover — Confirm Action: Inline confirmation popover for destructive actions with delete and cancel buttons.
  • Popover — Filter Panel: Popover with checkbox filters and apply/reset actions.
  • usePopover — Quick Actions: Custom quick-actions popover using usePopover for trigger refs, ARIA attributes, and focus trapping.
  • Popover — Keyboard Shortcuts: Popover displaying a list of keyboard shortcuts with key and description pairs.
  • Popover — Settings Panel: Popover with toggle switches for managing user preferences like notifications, dark mode, and sounds.

Complete example dependencies

Some examples import optional icons. The per-example install command in the examples list identifies exactly what that example needs. For all examples on this page:

npm install @heroicons/react@2.2.0

Source StyleX compilation

PopoverBottomSheetAlternative contain stylex.create and require a StyleX source build pipeline. Precompiled Core CSS alone does not compile these examples. See Styling before copying them.