Popover
A small panel anchored to a button that holds a few controls or a short explanation.
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Snooze
</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Snooze alert</PopoverTitle>
<PopoverDescription>
Hide it from the queue. It comes back if the rule fires again.
</PopoverDescription>
</PopoverHeader>
<div className="grid grid-cols-3 gap-2">
<Button variant="outline" size="sm">
1 hour
</Button>
<Button variant="outline" size="sm">
4 hours
</Button>
<Button variant="outline" size="sm">
1 day
</Button>
</div>
</PopoverContent>
</Popover>Usage
import { Button } from "@zenoxai/ui/components/button"
import {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@zenoxai/ui/components/popover"
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>Snooze</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Snooze alert</PopoverTitle>
<PopoverDescription>Hide it from the queue.</PopoverDescription>
</PopoverHeader>
{/* controls */}
</PopoverContent>
</Popover>
A popover opens on click next to its trigger and closes on Escape or a click outside. It is not modal: the rest of the page stays usable. Use it for a few related controls that belong to one button (snooze options, a “save view” field, a quick filter) or for an explanation the user asks for.
| Need | Use |
|---|---|
| A list of actions or options to pick from | Dropdown Menu |
| A task that needs the user’s full attention, or more than a couple of fields | Dialog |
| A one-line label for an icon button | Tooltip |
| A preview of what a link points to, on hover | Hover Card |
PopoverContent is 18rem wide, opens below the trigger with its center aligned, and lays out its
children in a column with a small gap. Change the width with className, the position with
side, align, sideOffset and alignOffset.
Examples
With a form
Keep a popover form to one or two fields and a single action. Anything longer belongs in a Dialog or a Sheet.
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Save view
</PopoverTrigger>
<PopoverContent align="start">
<PopoverHeader>
<PopoverTitle>Save view</PopoverTitle>
<PopoverDescription>
Keeps the current filters and columns under a name.
</PopoverDescription>
</PopoverHeader>
<Field>
<FieldLabel htmlFor="view-name">Name</FieldLabel>
<Input id="view-name" placeholder="Critical alerts, Acme Corp" />
</Field>
<Button size="sm" className="self-end">
Save view
</Button>
</PopoverContent>
</Popover>Side
side is bottom by default. The popover flips to the opposite side and shifts along the edge
when the preferred position would overflow the viewport.
<>
{(["top", "right", "bottom", "left"] as const).map((side) => (
<Popover key={side}>
<PopoverTrigger render={<Button variant="outline" />}>
{side}
</PopoverTrigger>
<PopoverContent side={side} className="w-56">
<PopoverDescription>
Opens on the {side}. It flips to the opposite side when there is
no room.
</PopoverDescription>
</PopoverContent>
</Popover>
))}
</>Open on hover
For an info icon that explains a figure, set openOnHover on the trigger. It still opens on
click and keyboard, so touch and screen reader users can reach the text, which a
Tooltip does not allow. delay sets the hover delay in milliseconds.
<div className="flex items-center gap-1.5">
<span>Risk score: 82</span>
<Popover>
<PopoverTrigger
openOnHover
delay={200}
render={
<Button
variant="ghost"
size="icon-xs"
aria-label="How the risk score is calculated"
/>
}
>
<InfoIcon />
</PopoverTrigger>
<PopoverContent side="top">
<PopoverHeader>
<PopoverTitle>Risk score</PopoverTitle>
<PopoverDescription>
Combines the severity of open alerts, exposed vulnerabilities and
how critical the asset is. Above 70 is high.
</PopoverDescription>
</PopoverHeader>
</PopoverContent>
</Popover>
</div>Accessibility
- The trigger gets
aria-expandedandaria-haspopup. Focus moves into the popover when it opens and returns to the trigger when it closes. PopoverTitleandPopoverDescriptionlabel and describe the popup. Use them instead of plain text when the popover has a heading.- An icon-only trigger needs an
aria-labelthat says what the popover holds.
API reference
Generated from the TypeScript source. className, style and children are accepted everywhere and omitted below.
Popover
Popover is a floating panel anchored to its PopoverTrigger and opened on click, for interactive content that does not need a page of its own: a filter form, a date picker, quick settings. For a short hint on hover use Tooltip; for a list of actions, DropdownMenu; for a task that must hold the user's focus, Dialog.
| Prop | Type | Default |
|---|---|---|
actionsRef | RefObject<PopoverRootActions | null>A ref to imperative actions. - `unmount`: Ends the closing phase of the popover after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the popover completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the popover imperatively when called. | - |
defaultOpen | booleanWhether the popover is initially open. | false |
defaultTriggerId | string | nullID of the trigger that the popover is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open popover. | - |
handle | PopoverHandle<unknown>A handle to associate the popover with a trigger. If specified, allows external triggers to control the popover's open state. | - |
modal | boolean | "trap-focus"Determines if the popover enters a modal state when open. - `true`: user interaction is limited to the popover: document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the popover, but document page scroll is not locked and pointer interactions outside of it remain enabled. | false |
onOpenChange | ((open: boolean, eventDetails: PopoverRootChangeEventDetails) => void)Event handler called when the popover is opened or closed. | - |
onOpenChangeComplete | ((open: boolean) => void)Event handler called after any animations complete when the popover is opened or closed. | - |
open | booleanWhether the popover is currently open. | - |
triggerId | string | nullID of the trigger that the popover is associated with. This is useful in conjunction with the `open` prop to create a controlled popover. There's no need to specify this prop when the popover is uncontrolled (that is, when the `open` prop is not set). | - |
PopoverContent
PopoverContent is the panel, 288px wide, in a portal below the trigger.
| Prop | Type | Default |
|---|---|---|
align | "center" | "start" | "end"How to align the popup relative to the specified side. | start |
alignOffset | number | OffsetFunctionAdditional offset along the alignment axis in pixels. Also accepts a function that returns the offset to read the dimensions of the anchor and positioner elements, along with its side and alignment. | 0 |
finalFocus | boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | void | HTMLElement | null)Determines the element to focus when the popover is closed. | - |
initialFocus | boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | void | HTMLElement | null)Determines the element to focus when the popover is opened. By default, focus moves to the first tabbable element inside the popup, except when the popover is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard. | - |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverPopupState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
side | "left" | "right" | "top" | "bottom" | "inline-end" | "inline-start"Which side of the anchor element to align the popup against. May automatically change to avoid collisions. | bottom |
sideOffset | number | OffsetFunctionDistance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. | 0 |
PopoverDescription
| Prop | Type | Default |
|---|---|---|
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverDescriptionState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
PopoverHeader
PopoverHeader stacks a PopoverTitle and a PopoverDescription at the top.
No props of its own. Accepts the props of the element it renders.
PopoverTitle
PopoverTitle names the panel, at the compact title step (text-md, semibold): the panel's text and controls sit at 12.5px, so the title needs the larger size and the heavier weight to stand apart.
| Prop | Type | Default |
|---|---|---|
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTitleState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |
PopoverTrigger
| Prop | Type | Default |
|---|---|---|
closeDelay | numberHow long to wait before closing the popover that was opened on hover. Specified in milliseconds. | 0 |
delay | numberHow long to wait before the popover may be opened on hover. Specified in milliseconds. | 300 |
handle | PopoverHandle<unknown>A handle to associate the trigger with a popover. | - |
nativeButton | booleanWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). | true |
openOnHover | booleanWhether the popover should also open when the trigger is hovered. | false |
payload | unknownA payload to pass to the popover when it is opened. | - |
render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverTriggerState>Allows you to replace the component's HTML element with a different tag, or compose it with another component. | - |