Popover

Anchors interactive content to a trigger without blocking the page.

import { Popover } from "@usefragments/ui";Source

Preview

<Popover>
  <Popover.Trigger render={<Button variant="soft" />}>Open Popover</Popover.Trigger>
  <Popover.Content>
    <Popover.Close />
    <Popover.Title>Popover Title</Popover.Title>
    <Popover.Description>

Usage

Use when

  • Editing a value in place
  • Previews richer than a line of text
  • Filter and picker panels
  • Content that outgrows a tooltip

Avoid when

  • A short hint (use Tooltip)
  • A list of actions (use Menu)
  • Anything that must block the page (use Dialog)
  • Feedback the user does not act on (use Toast or Alert)

Implementation notes

  • One job per panel
  • Always give the user a visible way out
  • Pick a side that does not cover what the user just clicked
  • Add the arrow when the trigger is not obvious
  • Pass render={<Button />} (or a link) to Popover.Trigger and Popover.Close

Installation

pnpm add @usefragments/ui

The npm package stays managed by your package manager. Import styles once from @usefragments/ui/styles. See the installation guide for framework and theme setup.

Anatomy

Import Popover once, then compose its parts with dot notation.

  • Popover.Trigger
  • Popover.Content
  • Popover.Close
  • Popover.Title
  • Popover.Description
  • Popover.Body
  • Popover.Footer

Title, body, and a close control.

<Popover>
  <Popover.Trigger render={<Button variant="soft" />}>Open Popover</Popover.Trigger>
  <Popover.Content>
    <Popover.Close />
    <Popover.Title>Popover Title</Popover.Title>
    <Popover.Description>
      This is a popover with some content. It can contain text, forms, or other elements.
    </Popover.Description>
  </Popover.Content>
</Popover>

Examples

With Form

Enough room to edit a value in place.

<Popover>
  <Popover.Trigger render={<Button variant="soft" />}>Edit Name</Popover.Trigger>
  <Popover.Content size="sm">
    <Popover.Close />
    <Popover.Title>Edit Name</Popover.Title>
    <Popover.Body>

With Arrow

The arrow ties the panel back to its trigger.

<Popover>
  <Popover.Trigger render={<Button variant="soft" />}>Info</Popover.Trigger>
  <Popover.Content arrow>
    <Popover.Title>Quick Tip</Popover.Title>
    <Popover.Description>
      This popover has an arrow pointing to its trigger element.

Positions

Flips to the opposite side when space runs out.

<div style={{ display: 'flex', gap: '16px', padding: '60px' }}>
  <Popover>
    <Popover.Trigger render={<Button variant="soft" />}>Top</Popover.Trigger>
    <Popover.Content side="top" size="sm">
      <Popover.Description>Popover on top</Popover.Description>
    </Popover.Content>

render lets a link open it.

<Popover>
  <Popover.Trigger render={<a href="#popover-help" />}>Open help</Popover.Trigger>
  <Popover.Content size="sm">
    <Popover.Description>The trigger renders as a link through render.</Popover.Description>
  </Popover.Content>
</Popover>

API reference

Component props
Prop
Type
Default
Description
Body.childrenRequiredReact.ReactNodeNot set—
childrenRequiredReact.ReactNodeNot setPopover trigger and content
Content.align"center" | "start" | "end" | undefinedcenterstartendNot set—
Content.anchorElement | null | undefinedNot setPosition against an element other than the trigger (a node in a host page). Defaults to the trigger.
Content.arrowboolean | undefinedNot setDraw a fill-only pointer toward the trigger.
Content.childrenRequiredReact.ReactNodeNot set—
Content.containerHTMLElement | ShadowRoot | null | undefinedNot setRender the portal into a specific container instead of `document.body` (a shadow root whose styles would not otherwise reach body-portaled content).
Content.positionMethod"absolute" | "fixed" | undefinedabsolutefixedNot setCSS `position` for the floating content.
Content.side"top" | "bottom" | "left" | "right" | undefinedtopbottomleftrightNot set—
Content.sizePopoverSize | undefinedsmmdNot setWidth cap and inset.
defaultOpenboolean | undefinedfalseDefault open state (uncontrolled)
Description.childrenRequiredReact.ReactNodeNot set—
Footer.childrenRequiredReact.ReactNodeNot set—
modalboolean | undefinedfalseWhether to block page interaction
onOpenChange((open: boolean) => void) | undefinedNot setCalled when open state changes
openboolean | undefinedNot setControlled open state
Title.childrenRequiredReact.ReactNodeNot set—

Accessibility

  • Focus is moved to popover content on open
  • Closes on Escape key
  • Focus returns to trigger on close
  • TooltipalternativeUse Tooltip for brief, non-interactive hints
  • MenualternativeUse Menu for action lists
  • DialogalternativeUse Dialog for blocking interactions

Next steps