Popover
A floating panel anchored to a trigger for arbitrary controls and content.
Show codeHide code
Installation
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
PopoverTitle,
PopoverDescription,
PopoverClose,
} from "@makeplane/propel/components/popover";Usage
A popover is a trigger-anchored panel for arbitrary content — a form, a property sheet, or a short
explanation — not a menu. Compose PopoverTrigger onto any control via render, then put the
panel in PopoverContent. Tall rich stacks go in PopoverBody. The panel positions relative to
the trigger that opened it.
import { Button } from "@makeplane/propel/components/button";
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from "@makeplane/propel/components/popover";
export default function UsageDemo() {
return (
<Popover>
<Button
stretch="auto"
variant="secondary"
size="lg"
render={<PopoverTrigger />}
label="About"
/>
<PopoverContent variant="text">
<PopoverTitle>Heading text</PopoverTitle>
<PopoverDescription>
A popup that displays information related to an element.
</PopoverDescription>
</PopoverContent>
</Popover>
);
}Anatomy
| Part | Role |
|---|---|
Popover |
Root context (open state, modality, handle, payload) |
PopoverTrigger / PopoverClose |
Behavior grafts onto Button / IconButton via render. Trigger: click, click+hover (openOnHover), or hover only (openOnHover + openOnClick={false}) |
PopoverContent |
Portal + positioner + popup. variant is rich (default) or text |
PopoverBody |
Scrollable slot for tall rich content. Title and actions stay outside so they stay pinned |
PopoverTitle / PopoverDescription |
Accessible name + supporting copy (especially on variant="text") |
rich is a 16px-padded, 12px-radius card with a 320px minimum. It grows with wider content; wrap
children only to force a different width. The panel caps at the height available next to the
trigger — put forms and long stacks in PopoverBody so they scroll; keep PopoverTitle and
actions as siblings. When the body can overflow, pass tabIndex={0}. text is a compact 296px
card with tight title + description padding.
Positioning lives on PopoverContent: side (default bottom), align (default start), plus
the usual offset, collision, and anchor props. Pass a different trigger through PopoverTrigger’s
render; the panel always anchors to the control that opened it.
Examples
Text
variant="text" is the compact title + description card. PopoverTitle and PopoverDescription
wire aria-labelledby / aria-describedby on the panel.
Show codeHide code
Open on hover
openOnHover (with a tuned delay) opens the panel on hover in addition to click. Keyboard and
touch still work.
Show codeHide code
Hover only
Pass openOnHover with openOnClick={false} when a mouse click should not toggle the panel —
glanceable previews, for example. Keyboard (Enter/Space) and touch still open so the surface is not
pointer-locked.
Show codeHide code
Controlled
External state drives the popover via open and onOpenChange, so any control can open or close
the panel.
Show codeHide code
Detached trigger
createPopoverHandle() links a trigger that lives outside the Popover to it via the handle
prop, so a button can open a panel declared elsewhere in the tree. The panel still anchors to the
trigger that opened it.
Show codeHide code
Accessibility
The popup is role="dialog". Name it with PopoverTitle (preferred) or aria-label on
PopoverContent. PopoverDescription becomes the described-by text. The trigger exposes
aria-expanded / aria-haspopup from Base UI.
Keyboard: Enter / Space on the trigger opens the panel; Escape or an outside click dismisses
it and returns focus to the trigger. PopoverClose (typically a Cancel/Save Button) also
dismisses. openOnHover still leaves click available; openOnClick={false} suppresses only mouse
click — keyboard and touch still open.
Default modal={false} leaves the page interactive and does not trap focus. modal={true} locks
page scroll and outside pointers; modal="trap-focus" keeps focus in the panel without locking
the page. Either trapping mode needs PopoverClose inside the popup so touch screen readers can
leave. There is no backdrop — the panel does not dim the page.
API Reference
Popover
PopoverTrigger
The element that opens the popover. Renders a <button> by default; pass render to project the
trigger onto any control (Button, IconButton, a custom element). The panel anchors to
whichever trigger opened it.
Interaction: click only (default), click + hover (openOnHover), or hover only (openOnHover
and openOnClick={false}).
PopoverContent
A trigger-anchored content panel. Composes the popover portal + positioner + styled popup so a
consumer only writes the trigger and the panel body. Pass any trigger through PopoverTrigger's
render; pass variant="text" for the compact title + description card. Put tall rich content
in PopoverBody so it scrolls under the available-height cap.
PopoverBody
The scrollable slot inside PopoverContent. Grows to fill leftover space under the popup's
available-height cap and scrolls when the body is taller than that space. Wide descendants are
constrained by an inner overflow-x-clip wrapper so focus scrolling cannot pan sideways. Put
forms and long stacks here; keep PopoverTitle and action rows as siblings so they stay pinned.
When the body can overflow, pass tabIndex={0} so the scroll region is keyboard-reachable (axe
scrollable-region-focusable).
PopoverTitle
The accessible title for the popover: Base UI's Popover.Title behavior (links to the popup via
aria-labelledby) grafted onto the family-owned title chrome (text-body-sm-medium).
PopoverDescription
Supporting text for the popover: Base UI's Popover.Description behavior (wired as the popup's
aria-describedby target) grafted onto the shared internal/OverlayDescription at md (body-xs
secondary).
PopoverClose
The behavior that closes the popover when activated. Use as the render target of a Button or
IconButton so the styled primitive's look wins via render-composition:
``tsx
<Button
variant="secondary"
size="sm"
stretch="auto"
label="Cancel"
render={<PopoverClose />}
/>;
Maps 1:1 to Popover.Close`.