Popover
An overlay positioned relative to its trigger, built on React Aria. Placement is collision-aware, so a popover near the viewport edge flips rather than clipping. While it's open, focus is contained inside; Esc and a press outside close it.
import { Popover } from "@pockets-ui/components"
import { button } from "@/styled-system/recipes"Reach for a popover when the content is interactive or worth dwelling on. For a short, non-interactive description on hover or focus, use Tooltip instead.
Anatomy
| Part | Role |
|---|---|
Popover.Root | Wires the trigger to the overlay and owns open state. |
Popover.Trigger | The focusable element that opens the popover. |
Popover.Content | The positioned surface — focus container and your content. |
Popover.Trigger renders its own element rather than wrapping a
Button, so these examples style it with the button
recipe — button({ variant: "solid" }) gives the same classes the
component uses.
Basic
Popover.Content wraps a dialog, which needs an accessible name. Give it
aria-label when the content has no visible heading to point at.
Code
import { Popover } from "@pockets-ui/components"
import { css } from "@/styled-system/css"
import { button } from "@/styled-system/recipes"
// Popover.Trigger renders its own element, so style it with the button recipe
// rather than nesting a <Button> inside.
const trigger = button({ variant: "solid" })
const heading = css({ fontWeight: "semibold", color: "fg" })
const body = css({ fontSize: "sm", color: "fg.subtle", lineHeight: "relaxed" })
export default function PopoverBasic() {
return (
<Popover.Root>
<Popover.Trigger className={trigger}>Show details</Popover.Trigger>
<Popover.Content aria-label="Order details">
<p className={heading}>Order #1024</p>
<p className={body}>
Shipped 2 days ago via ground. Estimated delivery is tomorrow by 8pm.
</p>
</Popover.Content>
</Popover.Root>
)
}Placement
placement is the preferred side and defaults to "bottom start"; React Aria
moves the popover if it won't fit there. offset controls the gap from the
trigger and defaults to 8.
Code
import { Popover } from "@pockets-ui/components"
import { css } from "@/styled-system/css"
import { button } from "@/styled-system/recipes"
const trigger = button({ variant: "solid" })
const body = css({ fontSize: "sm", color: "fg.subtle", lineHeight: "relaxed" })
export default function PopoverPlacement() {
return (
<>
<Popover.Root>
<Popover.Trigger className={trigger}>Top</Popover.Trigger>
<Popover.Content placement="top" aria-label="Shown above">
<p className={body}>Shown above the trigger.</p>
</Popover.Content>
</Popover.Root>
<Popover.Root>
<Popover.Trigger className={trigger}>Bottom</Popover.Trigger>
<Popover.Content placement="bottom" aria-label="Shown below">
<p className={body}>Shown below the trigger.</p>
</Popover.Content>
</Popover.Root>
<Popover.Root>
<Popover.Trigger className={trigger}>Left</Popover.Trigger>
<Popover.Content placement="left" aria-label="Shown to the left">
<p className={body}>Shown to the left.</p>
</Popover.Content>
</Popover.Root>
<Popover.Root>
<Popover.Trigger className={trigger}>Right</Popover.Trigger>
<Popover.Content placement="right" aria-label="Shown to the right">
<p className={body}>Shown to the right.</p>
</Popover.Content>
</Popover.Root>
</>
)
}