Skip to main content
Gremorie

Popover

Anchored, click-driven overlay for interactive contextual content. Built on Radix Popover.

Overview

Popover is the right primitive for anchored content that responds to an intentional click: date pickers, color pickers, mini forms, share menus, share/like inline actions. It is distinct from Tooltip (hover-only, text-only, never interactive) and HoverCard (hover-only previews of non-critical content). When the content is too long or warrants blocking the page, escalate to Dialog or Sheet.

The default surface is w-72, padded p-4, with a Radix-driven transform-origin so the open animation feels anchored to the trigger.

Preview

Installation

bash npx gremorie@latest add rx-popover
bash pnpm dlx gremorie@latest add rx-popover
bash yarn dlx gremorie@latest add rx-popover
bash bunx --bun gremorie@latest add rx-popover

Usage

import { Button } from "@gremorie/rx-forms";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@gremorie/rx-overlays";

export function Example() {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button variant="outline">Open</Button>
      </PopoverTrigger>
      <PopoverContent>
        <p className="text-sm">Anchored interactive content.</p>
      </PopoverContent>
    </Popover>
  );
}

Angular edition planned for Phase 5h. Star the repo to track progress.

API

<Popover>

Extends Radix Popover.Root. Notable props:

PropTypeDefaultDescription
openboolean-Controlled open state.
defaultOpenbooleanfalseInitial open state when uncontrolled.
onOpenChange(open: boolean) => void-Fired when open state changes.
modalbooleanfalseWhen true, locks body scroll and outside interaction.

<PopoverTrigger>

Extends Radix Popover.Trigger. Pair with asChild to forward styles to a Button or any custom trigger.

<PopoverContent>

PropTypeDefaultDescription
align"start" | "center" | "end""center"Alignment relative to the trigger axis.
sideOffsetnumber4Pixel gap between trigger and content.
side"top" | "right" | "bottom" | "left""bottom"Preferred side; flips automatically when there is no room.
alignOffsetnumber0Offset along the alignment axis.
avoidCollisionsbooleantrueAuto-reposition to stay in the viewport.
collisionPaddingnumber | object0Distance from viewport edges to maintain.

Wrapped in a Radix Portal. All other Radix Popover.Content props are forwarded.

<PopoverAnchor>

Optional. Anchors the content to an element different from the trigger. Useful when the visual anchor and the click target are not the same element.

<Popover>
  <PopoverAnchor asChild>
    <span>Visual anchor</span>
  </PopoverAnchor>
  <PopoverTrigger asChild>
    <Button>Click me</Button>
  </PopoverTrigger>
  <PopoverContent>Anchored to the span, not the button.</PopoverContent>
</Popover>

<PopoverHeader>, <PopoverTitle>, <PopoverDescription>

Optional layout helpers. Header stacks title and description; title is font-medium; description is text-muted-foreground.

Composition

  1. <Popover> owns the open/close state.
  2. <PopoverTrigger asChild> wraps the focusable element that opens it.
  3. <PopoverContent> mounts via Portal, anchored to the trigger (or PopoverAnchor).
  4. Inside content: any interactive UI - forms, command lists, color pickers, sliders.
  5. Dismissal: click outside, press Esc, or close programmatically.

Variations

Settings popover

Mini form with header. Stays open while the user toggles preferences.

<Popover>
  <PopoverTrigger asChild>
    <Button variant="outline">Quick settings</Button>
  </PopoverTrigger>
  <PopoverContent>
    <PopoverHeader>
      <PopoverTitle>Quick settings</PopoverTitle>
      <PopoverDescription>
        Tweak preferences for this session.
      </PopoverDescription>
    </PopoverHeader>
    <div className="mt-3 flex flex-col gap-3">
      <Field>
        <FieldLabel htmlFor="theme">Theme</FieldLabel>
        <Select id="theme" defaultValue="system">
          <option value="light">Light</option>
          <option value="dark">Dark</option>
          <option value="system">System</option>
        </Select>
      </Field>
    </div>
  </PopoverContent>
</Popover>

Aligned to trigger end

Right-align the popover under a toolbar icon so it doesn't overflow the viewport.

<Popover>
  <PopoverTrigger asChild>
    <Button variant="ghost" size="icon" aria-label="More">
      <MoreHorizontalIcon />
    </Button>
  </PopoverTrigger>
  <PopoverContent align="end" sideOffset={8}>
    {/* menu content */}
  </PopoverContent>
</Popover>

With custom anchor

Use PopoverAnchor to decouple the visual anchor from the click target (common in selection-driven UIs where a highlighted range opens the popover).

<Popover open={open} onOpenChange={setOpen}>
  <PopoverAnchor virtualRef={selectionRef} />
  <PopoverContent>Formatting toolbar</PopoverContent>
</Popover>

Accessibility

  • Role: role="dialog" for the content (Radix default).
  • Keyboard: Esc closes; Tab cycles focus within the content; click outside closes (configurable).
  • Focus management: focus moves into the content on open and returns to the trigger on close.
  • Outside click: closes by default. Disable via onInteractOutside if the popover must persist.
  • aria-expanded: set on the trigger automatically; aria-controls links to the content id.
  • Reduced motion: open/close animations honor prefers-reduced-motion.
  • Tooltip - hover-only, non-interactive text label.
  • Hover Card - hover-only rich preview, non-interactive.
  • Dropdown Menu - menu-semantics popover with arrow navigation.
  • Dialog - blocking modal when content is too large or critical.

On this page