Skip to content
Monogem

Component · Containers & Overlays

Popover

Overview

Triggered, non-modal supplemental content anchored to a control — richer than a Tooltip (can hold interactive content: buttons, links, a small form), but unlike Dialog it never blocks or dims the rest of the page. Its behavior — focus placement on open, collision-aware positioning, and close-on-outside-interaction — is specified below.

Anatomy

Trigger (any control) → Anchor relationship → Popover surface (Header/title optional, Body content, optional Footer/actions).

  • Trigger — any control that opens the popover.
  • Surface — the floating container anchored to the trigger.
  • Header — optional title.
  • Body — free-form content.
  • Footer — optional actions.

Popover has no fixed internal anatomy beyond the surface itself — unlike Dialog, it doesn't mandate a Title/Description/Actions split; content is free-form, sized to what the instance needs.

Live Example

Informational — a data readout

Storage used

Interactive — a small settings form

Code Example

popover-showcase.tsx
"use client";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Popover,
PopoverClose,
PopoverContent,
PopoverTitle,
PopoverTrigger,
} from "@/components/ui/popover";

/**
* Live Popover example — anchored, non-modal supplemental content. The page
* behind stays fully operable: no scrim, no scroll lock. `Escape` closes and
* returns focus to the trigger; a click outside closes it and leaves focus
* where the click landed; clicking the trigger again toggles it.
*
* - Informational: no focusable content, so focus stays on the trigger when it
* opens. It is still a Popover (toggled by a trigger, more structure than a
* Tooltip), not a Tooltip.
* - Interactive: contains fields and a button, so opening it from the keyboard
* moves focus to the first field. It carries an accessible name via
* `<PopoverTitle>` (wired through `aria-labelledby`).
*/
export function PopoverShowcase() {
return (
<div className="flex flex-col gap-8">
<section className="flex flex-col gap-3">
<h3 className="text-sm font-medium leading-snug text-muted-foreground">
Informational — a data readout
</h3>
<div className="flex items-center gap-2 text-sm leading-normal">
<span>Storage used</span>
<Popover>
<PopoverTrigger>
<Button size="sm" variant="outline">
Details
</Button>
</PopoverTrigger>
<PopoverContent aria-label="Storage breakdown">
<dl className="grid grid-cols-[1fr_auto] gap-x-6 gap-y-1">
<dt className="text-muted-foreground">Documents</dt>
<dd>4.2 GB</dd>
<dt className="text-muted-foreground">Images</dt>
<dd>11.8 GB</dd>
<dt className="text-muted-foreground">Backups</dt>
<dd>2.1 GB</dd>
</dl>
</PopoverContent>
</Popover>
</div>
</section>

<section className="flex flex-col gap-3">
<h3 className="text-sm font-medium leading-snug text-muted-foreground">
Interactive — a small settings form
</h3>
<Popover>
<PopoverTrigger>
<Button variant="outline">Edit dimensions</Button>
</PopoverTrigger>
<PopoverContent className="w-64 max-w-none">
<PopoverTitle>Dimensions</PopoverTitle>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-1.5">
<Label htmlFor="popover-width">Width</Label>
<Input id="popover-width" size="sm" defaultValue="1920" />
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="popover-height">Height</Label>
<Input id="popover-height" size="sm" defaultValue="1080" />
</div>
<div className="flex justify-end gap-2">
<PopoverClose>
<Button size="sm" variant="ghost">
Cancel
</Button>
</PopoverClose>
<PopoverClose>
<Button size="sm">Apply</Button>
</PopoverClose>
</div>
</div>
</PopoverContent>
</Popover>
</section>
</div>
);
}

Variants

Content modes: informational vs. interactive

A Popover's content is always exactly one of two modes, and the mode — not a per-instance judgment call — determines focus behavior on open. Any link, button, form control, or other interactive element inside the Popover makes it Interactive; there is no ambiguous middle case (e.g. "a details panel that happens to contain one link").

Informational Popover

  • Contains no links, buttons, form controls, or other interactive elements — plain content only (text, an image, a data readout).
  • Focus stays on the trigger when the Popover opens.
  • The Popover is programmatically associated with the trigger (aria-expanded/aria-controls, or aria-describedby where that fits the content better) without stealing focus.
  • It's still a Popover, not reclassified as a Tooltip merely because it's non-interactive — its visibility is explicitly toggled by a trigger action, and it may hold more structure (a title, multiple lines, an image) than a Tooltip's plain text line allows.

Interactive Popover

  • Any Popover containing a link, button, form field, or other control is Interactive — no exception for "just one link."
  • It must have an accessible name: a visible title inside the Popover referenced via aria-labelledby, or an equivalent programmatic label (e.g. aria-label on the surface) when no visible title is used.
  • Opening by keyboard moves focus to the first meaningful interactive element inside the Popover, or to an appropriately focusable content container when no single element is the obvious first stop.
  • Focus is not trapped — see Interactive content.
  • Escape closes it and restores focus to the trigger.
  • Outside interaction may dismiss it.

States

StateLook
ClosedNot rendered
Open--popover fill, --popover-foreground text, --shadow-md, --border edge

Usage Guidance

Purpose and when to use it

Use Popover for content that's genuinely optional to the current task, triggered on demand, and doesn't need to interrupt the user — a details panel for a data point, a small settings form attached to a control, a share panel. The page behind it stays fully operable.

When not to use it

  • Passive, non-interactive labeling — that's Tooltip.
  • Something that must block the current flow until resolved (a confirmation, a required decision) — that's Dialog.
  • A list of navigable options/commands (an actions menu, a select-style list) — that's a dropdown menu pattern (role="menu"/role="listbox", roving arrow-key selection), which is a distinct component (Dropdown Menu / Select), not a Popover. Reaching for Popover to build a menu skips that pattern's real keyboard and semantic requirements.

Anchor relationship

The Popover is positioned relative to its trigger (anchor), not the viewport — it moves with its trigger and closes if the trigger leaves the viewport (e.g. scrolled out of view in a scrollable container).

Dismissal expectations

TriggerBehavior
EscapeAlways closes the popover and returns focus to the trigger
Click outside (outside both trigger and popover)Closes the popover; focus stays wherever the click landed (not forced back to the trigger — the user deliberately moved elsewhere)
Trigger clicked againToggles closed
Focus moves within the trigger–popover relationship (e.g. trigger → its own popover content)Never dismisses the popover
Focus leaves both the trigger and the popover (e.g. Tab out to the next page element)May dismiss the popover, when that matches the instance's interaction model — an Informational popover with no focusable content has nothing to Tab out of in the first place

Interactive content

A Popover may contain buttons, links, inputs, or a small form. Because it's non-modal, focus is not trapped inside it the way Dialog's is — a user can Tab out of the popover into the rest of the page, which typically (implementation-dependent) closes it, since it's no longer the relevant context.

Positioning and collision handling

Default placement is anchored to one side of the trigger (commonly below, left-aligned to the trigger's edge); on viewport collision, it should flip to the opposite side before shifting along the cross-axis — the same collision-avoidance intent Tooltip uses at smaller scale. Measurement and repositioning happen at render time against the real viewport. Collision handling is against the viewport only — it doesn't account for every scrollable ancestor — so a Popover inside a nested scroll container may need an explicit side.

Tokens

Popover uses existing Monogem semantic tokens to stay consistent with the design system; it introduces no component-specific styling values.

TokenWhere used
--popover / --popover-foregroundSurface fill + text — Color names "Menus, tooltips" for this pair; Popover shares it
--borderSurface edge
--shadow-mdElevation — Shadows names "Dropdowns, popovers, hover"
--radius-lgCorner radius — Radius explicitly names "Cards, popovers" for this token
--ringFocus ring on any focusable content inside the popover
--scale-4Surface padding

Do / Don’t

Do

  • Keep Popover content focused on one task or one piece of information.
  • Return focus to the trigger on Escape.
  • Close the popover when its trigger scrolls out of view.

Don’t

  • Use Popover to build a command/options menu — use a real menu pattern instead.
  • Trap focus inside a Popover — that's Dialog's contract, not Popover's.
  • Add a full-page backdrop/scrim behind a Popover — that visual weight belongs to Dialog.

Accessibility

  • Trigger sets aria-expanded (true/false) and aria-controls pointing at the popover's id.
  • The popover surface itself is role="dialog" with aria-modal="false" (or an equivalent non-modal container role) — non-modal, so background content remains in the accessibility tree and operable, unlike Dialog's modal contract.
  • Every interactive element inside the popover follows its own component's accessibility contract (a Button inside a Popover is still a Button) — Popover adds no new interaction pattern of its own.
  • An Interactive Popover (see Content modes) carries an accessible name — a visible title referenced by aria-labelledby, or an aria-label on the surface — required once it contains any focusable control, not optional.

Responsive Behavior

An anchored popover can be awkward on a narrow viewport — too little room to position it sensibly next to a small trigger. Below --breakpoint-sm, an implementation may fall back to a bottom-anchored presentation closer to Sheet's Bottom placement. This is a documented intent, not a fully specified fallback component.

Related Components / Patterns

  • Tooltip — a short label on hover or keyboard focus.
  • Dialog — modal interruption until the user resolves it.
  • Dropdown Menu — a menu of commands or actions.