atom

Popover

Floating content panel anchored to a trigger element with configurable placement, auto-flip, and dismiss behavior.

Overview

The Popover component renders a floating content panel anchored to a trigger element. It supports 6 placement positions with automatic flip when near viewport edges, and dismisses on outside click or Escape key. Popover is the foundational building block for AutocompleteSelect, MultiSelectDropdown, and ActionMenu.

import { Popover, Button } from '@enara-health/ui-react';

const [isOpen, setIsOpen] = useState(false);

<Popover
  open={isOpen}
  onOpenChange={setIsOpen}
  trigger={<Button>Open Popover</Button>}
>
  <p>Popover content goes here.</p>
</Popover>

Note: Popover is an interactive component that requires state management. To see it in action, run nx dev @enara-health/ui-react and view the component in the dev preview.

Props

Prop Type Default Description
open boolean - Required. Whether the popover is visible
onOpenChange (open: boolean) => void - Required. Callback when open state changes
trigger ReactNode - Required. Element that anchors and toggles the popover
children ReactNode - Required. Content rendered inside the floating panel
placement 'bottom-start' | 'bottom-end' | 'bottom' | 'top-start' | 'top-end' | 'top' 'bottom-start' Preferred position relative to the trigger
contentPadding 'none' | 'default' 'default' Padding inside the floating panel
matchTriggerWidth boolean false Whether the popover should match the trigger's width
className string - Additional CSS class names for the popover panel

Variants

Placement

Popover supports 6 placement positions. When there isn't enough space in the preferred direction, it automatically flips to the opposite side.

bottom-start default
bottom
bottom-end
top-start
top
top-end
<Popover placement="bottom-start" ...>  {/* Left-aligned below trigger */}
<Popover placement="bottom" ...>        {/* Centered below trigger */}
<Popover placement="bottom-end" ...>    {/* Right-aligned below trigger */}
<Popover placement="top-start" ...>     {/* Left-aligned above trigger */}
<Popover placement="top" ...>           {/* Centered above trigger */}
<Popover placement="top-end" ...>       {/* Right-aligned above trigger */}

Content Padding

Use contentPadding="none" when the popover contains custom layouts like menu lists or autocomplete results that manage their own spacing.

// Default padding (for general content)
<Popover open={isOpen} onOpenChange={setIsOpen} trigger={<Button>Info</Button>}>
  <p>Some helpful information.</p>
</Popover>

// No padding (for custom layouts like menus)
<Popover open={isOpen} onOpenChange={setIsOpen} trigger={<Button>Menu</Button>} contentPadding="none">
  <MenuList items={menuItems} />
</Popover>

Match Trigger Width

When matchTriggerWidth is true, the popover panel matches the width of its trigger element. Useful for select-like dropdowns.

<Popover
  open={isOpen}
  onOpenChange={setIsOpen}
  trigger={<Button style={{ width: 200 }}>Select Option</Button>}
  matchTriggerWidth
>
  <OptionsList />
</Popover>

Examples

Dropdown Filter Panel

Use a popover to show filter options below a trigger button.

const [isOpen, setIsOpen] = useState(false);

<Popover open={isOpen} onOpenChange={setIsOpen} trigger={<Button>Filter</Button>}>
  <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
    <Checkbox label="Active" checked={filters.active} onChange={handleToggle('active')} />
    <Checkbox label="Pending" checked={filters.pending} onChange={handleToggle('pending')} />
    <Checkbox label="Archived" checked={filters.archived} onChange={handleToggle('archived')} />
  </div>
</Popover>

Contextual Info

Display additional information on demand without navigating away.

<Popover
  open={isOpen}
  onOpenChange={setIsOpen}
  trigger={<IconButton icon={<Info />} aria-label="More info" />}
  placement="bottom-end"
>
  <Text size="sm" color="muted">
    This metric is calculated from the last 30 days of activity.
  </Text>
</Popover>

Accessibility

  • Keyboard Support: Escape key closes the popover. Trigger inherits keyboard activation from its element type.
  • Focus Management: Focus remains with the trigger element. Consumers should manage focus within popover content as needed.
  • Screen Reader: Content is only in the DOM when open, preventing hidden content from being announced.
  • ARIA: Consumers should add appropriate ARIA attributes (e.g., aria-haspopup, aria-expanded) to the trigger based on the use case.
  • WCAG Compliance: Meets AA standards when properly configured with ARIA attributes.

Best Practices

  • Add aria-haspopup and aria-expanded to trigger elements for screen readers
  • Don't use Popover for tooltip-like content — use Tooltip instead
  • Don't use Popover for modal interactions — use Dialog instead
  • The trigger must be a single React element (not a string or fragment)
  • Keep popover content focused and concise