Popover

A non-modal anchored panel you style yourself - flips sides and clamps to the viewport as the trigger moves.

Popover playground

Popover ships placement only - the surface is yours to paint. Picking a slot leaves the panel open; dismissal is on you.

Install Popover

1. New project? One command installs the package and wires everything - see Installation:

bash
pnpm dlx zyncat-ui init

2. Import the component - it loads its own stylesheet:

tsx
import { Popover } from '@zyncat/ui/popover';

Usage

tsx
import { Popover } from '@zyncat/ui/popover';
 
<Popover trigger={<Button variant="secondary">Snooze</Button>} side="bottom" align="start" open={open} onOpenChange={setOpen}>
<div className="snooze-panel">...</div>
</Popover>

Compose Popover in your application. No Tailwind or external styling library is required; every value resolves from Zyncat UI's token vocabulary.

Popover props

Popover

PropTypeDefaultDescription
openboolean—Controlled open state. Omit to stay uncontrolled.
defaultOpenboolean—Initial state when uncontrolled. Default false.
onOpenChange(open: boolean) => void—Fires whenever the open state changes. Pair with `open` for controlled use.
triggerReactElement | null—Cloned to toggle the panel, and used as the anchor unless `anchor` is set.
activateOnActivateOn'click'Whether the trigger fires on `pointerdown` (snappier) or waits for `click`.
anchorVirtualAnchor | null—Anchor to an arbitrary rect instead of the trigger - any `{ getBoundingClientRect() }`, which an element also satisfies. Drive `open` yourself; pass a new object to re-place a moving anchor.
side'top' | 'bottom' | 'left' | 'right'—Preferred side; flips to the opposite side when cramped. Default 'bottom'.
align'start' | 'center' | 'end'—Cross-axis alignment against the trigger. Default 'start'.
arrowboolean—Caret tracking the trigger center. Default false.
dismissibleboolean—Esc + outside-press dismissal. Default true.
idstring—Base id for the panel; drives the trigger's `aria-controls`. Auto-generated when omitted.
...htmlAttributesHTMLAttributes<HTMLDivElement> & DataAttributes—Standard attributes (className, style, data-*, ...) forwarded to the popover panel.
animationDisableableAnimationopen 'base'/'entrance', close 'fast'/'exit'Open/close timing - motion tokens only, or `null` to disable.
children*ReactNode—The ENTIRE surface - paint AND semantics. Drive dismissal with `open`/`onOpenChange`.

VirtualAnchor

PropTypeDefaultDescription
getBoundingClientRect*DOMRect—

Frequently asked questions

Drive it yourself with open and onOpenChange - children is a plain ReactNode, not a function that hands you a close callback, so a row's own onClick calls your own setOpen(false). Left uncontrolled, dismissible (on by default) already closes it on Escape or an outside press with no extra wiring.

Pass anchor - anything with a getBoundingClientRect() method, which a plain element also satisfies - and drive open yourself; trigger becomes optional. Passing a new anchor object re-places it, which is how you follow a moving target like a context-menu point.

Yes - it flips to the opposite side when the preferred side is cramped and the opposite side has more room, and it clamps both axes so the panel never renders past the viewport edge. Position re-runs on scroll and resize.

Popover is headless - children is the entire surface, paint and semantics both, so you build the menu or card yourself. Dropdown is the pre-styled menu built on that same anchored positioning, with grouped rows, shortcuts and nested submenus; Tooltip is a single transient hint bubble that travels between triggers rather than a persistent panel.

Set in Geist & Newsreader — animated by the house engineZyncat UI · Rev 0.11 · MIT · Built by Tabsir Ahammed · Source on GitHub