Popover
Floating content anchored to a trigger element. Supports click-to-open (popover) and hover (infotip) modes with built-in 4-side positioning, automatic flip, shift, and viewport collision detection without a third-party positioning dependency.
Installation
dgadd is not public on npm yet. Until the first release, pack @diffgazer/add from the repository and install that tarball into this app, which is what puts dgadd on pnpm exec.
The hosted registry is not public yet because r.b4r7.dev does not resolve. Use this source checkout or a local registry preview until the endpoint returns 200.
Diffgazer packages are not yet published to npm. Until the first release, pack @diffgazer/ui and @diffgazer/keys from the repository and install those tarballs.
UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS once packages are available.
Package-mode consumers must install the required @diffgazer/keys peer alongside
@diffgazer/ui. Copy and dgadd installs rewrite the autofocus helper to local source.
Usage
Examples
Hover Mode
Placement
Actions menu
Controlled
API Reference
Popover
Popover.Trigger
Popover.Content
Data attributes
Accessibility
Keyboard Navigation
Click-mode triggers toggle with pointer or keyboard activation. Escape closes open content and returns focus to the trigger. Dialog and menu content can auto-focus on open. Tab follows the browser's normal order; the popover stays open while focus moves between its trigger and content, then closes when focus leaves both.
| Key | Action |
|---|---|
| Enter / Space | Toggles the trigger in click mode. |
| Escape | Closes open click-mode content and returns focus to the trigger. |
| Tab / Shift+Tab | Moves normally and closes click-mode content only after focus leaves the trigger/content pair. |
Notes
Requires @diffgazer/keys (package mode)
Popover's auto-focus on open (focusing the first focusable element in the content) imports from the required @diffgazer/keys peer. Package consumers must install @diffgazer/keys with @diffgazer/ui; packages are not yet published to npm, so until the first release install both from locally packed tarballs. Importing @diffgazer/ui/components/popover without keys fails at module load with an error naming the missing @diffgazer/keys package. Copy/dgadd consumers do not need the package — copy mode rewrites the focusable helper to local source.
Trigger Modes
Set `triggerMode="click"` (default) for interactive popovers that toggle on click. Set `triggerMode="hover"` for tooltip-like behavior with delay. Hover mode renders with `role="tooltip"` and content stays open while the pointer hovers it.
Positioning
Content is positioned relative to the trigger with `side` (top/bottom/left/right), `align` (start/center/end), `sideOffset`, and `alignOffset`. When `avoidCollisions` is true (default), content flips to the opposite side if it would overflow, then tries cross-axis sides, then shifts within the viewport.
Click Mode
In click mode, the popover toggles on trigger click, dismisses on outside click, Escape, or focus leaving the trigger/content pair. Content is interactive (pointer-events enabled). Use for forms, menus, or rich content.
Popup Role Contract
popupRole controls the trigger aria-haspopup value and accepts dialog, menu, listbox, tree, or grid. Hover mode content renders role="tooltip" automatically; tooltip is not a popupRole value. Consumers remain responsible for matching the role to the content pattern and supplying an accessible name for dialog-like content.
Controlled
Use `open` and `onOpenChange` props for controlled state. Works with both trigger modes.
Default Surface
Popover.Content ships the surface: a 1px --border hairline, a --surface-1 fill with a 1px --surface-1-highlight inner top lip, and the family's tight rounded-sm corners. --surface-1 sits one step off the page background (lighter in dark, darker in light), so a floating layer separates from dense content by depth step rather than by blur — there is still no drop shadow. There is no padding either, so menu-style content can sit flush. `className` merges last, so a consumer can override or drop any of it; FloatingPanel underneath stays fully headless.
Actions Menu
For a menu button, set popupRole="menu", give Popover.Content align="start" and no padding, and let Menu autoFocus with onClose closing the popover and onSelect closing it before running the pick; the popover then hands focus back to the trigger, whose aria-expanded carries the open state (a trigger sitting in a roving-focus row should drop its own highlight ring while open). MenuItem hotkey is a label only — bind the key in Menu onKeyDown, keep an entry the state cannot run listed and disabled with its reason instead of hiding it, and put the destructive entry last behind a MenuDivider. Inside the open menu ArrowUp/ArrowDown move the highlight and ArrowLeft/ArrowRight are inert (they only serve submenus); Escape and Tab close it. See the "Actions menu" example.
Portal Rendering
Content renders through the shared Portal primitive. When a PortalContainerProvider is present, Popover.Content uses that scoped container; otherwise it falls back to document.body. This keeps nested overlay trees in the same portal scope while still escaping overflow:hidden ancestors by default.
When role="dialog" is used without aria-label or aria-labelledby, the content falls back to the accessible name "Popover" so assistive technology never sees an unlabeled dialog. Provide your own label for any production usage; the fallback exists to prevent silent accessibility regressions during development.
Source
Install via CLI: pnpm exec dgadd add ui/popover. Keyboard hooks are included as standalone copies. For the full experience, use --integration keys.
Highlighted source loads after this disclosure opens. Browse the source repository.