Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository

Popover

A non-modal panel anchored to its trigger — for content that is richer than a tooltip but lighter than a dialog.

Usage#

import { Popover, PopoverContent, PopoverTrigger } from "neelam-ui";
 
<Popover>
  <PopoverTrigger>Dimensions</PopoverTrigger>
  <PopoverContent>
    <label>Width <Input size="sm" defaultValue="480" /></label>
  </PopoverContent>
</Popover>

Popover, dialog, or tooltip?#

  • Tooltip — a few words of label. Never focusable, never interactive.
  • Popover — interactive content the user can work through, without the page behind being blocked.
  • Dialog — an interruption that must be resolved before anything else. Traps focus and inerts the page.

PopoverContent is role="dialog", not role="menu" or "listbox" — its content is arbitrary rather than a list of choices. For a list of actions, use DropdownMenu.

Naming the panel#

A role="dialog" needs an accessible name; axe's aria-dialog-name rule flags its absence. Rather than making every caller remember that, PopoverContent defaults aria-labelledby to the trigger's own id — reusing whatever already names the trigger, for free.

Pass aria-label or aria-labelledby explicitly when the trigger's label does not double as a good name for the panel:

<PopoverTrigger aria-label="Account">
  <Avatar />
</PopoverTrigger>
<PopoverContent aria-label="Account settings">…</PopoverContent>

What the platform provides#

PopoverContent is a native popover (popover="auto"), so Escape and outside-click dismissal come from the browser, and top-layer rendering means the panel escapes any overflow: hidden ancestor without a portal.

On open, focus moves to the first focusable element inside — a form's first field — or to the panel itself when there is nothing focusable, as with a purely informational card. On close it returns to the trigger, through every dismissal path. The page behind is scroll-locked while the panel is open, so it cannot drift away from its anchor.

Non-modal, but scroll-locked

Scroll locking is positional, not a focus trap. Tab still leaves the popover and continues into the page — which is the difference between this and a Dialog.

Keyboard#

Keyboard shortcuts
KeyBehaviour
EnterSpaceToggles the popover when focus is on the trigger.
TabShift+TabMoves through the panel's contents, then out into the page — focus is not trapped.
EscapeCloses the popover and returns focus to the trigger.

Accessibility#

  • The trigger is a native <button> with aria-haspopup="dialog", aria-expanded, and aria-controls pointing at the panel — the WAI-ARIA non-modal dialog pattern.
  • The panel is role="dialog" and always resolves to an accessible name, from the trigger by default.
  • Focus moves in on open and back to the trigger on close, including on outside click, where it is easiest to lose.
  • Open and close transitions are dropped under prefers-reduced-motion.

API reference#

Popover#

Props for Popover
PropTypeDefault
defaultOpenbooleanfalse
onOpenChange((open: boolean) => void)
open

Controls the open state. Omit to let the popover manage its own state.

boolean

PopoverTrigger#

Adds no props of its own — it renders a native <button> and accepts every <button> attribute. The ARIA wiring (aria-haspopup, aria-expanded, aria-controls) is applied for you.

PopoverContent#

Adds no props of its own beyond the native <div> attributes. aria-label and aria-labelledby are the two worth knowing about — see Naming the panel.

PopoverClose#

Renders a native <button> that closes the popover. Accepts every <button> attribute.