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 ships in @diffgazer/add on npm: pnpm add -D @diffgazer/add puts it on pnpm exec, and npx @diffgazer/add runs it without the install.
UI components require Tailwind CSS v4. Local copy mode imports src/styles/styles.css; package mode uses @diffgazer/ui CSS from the installed package.
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; install both from npm (npm install @diffgazer/ui @diffgazer/keys), or take the component from the live registry instead (npx shadcn add https://r.b4r7.dev/r/ui/popover.json). 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.