Default trigger
The built-in 'i' button. Click to open, click outside or press Escape to dismiss.
<Popover title="How this works" align="left">
Billed monthly. Cancel any time
from your dashboard.
</Popover>Click-toggled info panel with animated tail and dismiss handling.
A click-toggled anchored panel with an animated entrance, pointer tail, and built-in dismiss handling (outside-click, Escape, re-click). Comes with a default info trigger but accepts any custom trigger via renderTrigger — bell icons, avatars, menu buttons.
Add the package, import the component. The popover ships with its own CSS — no setup, no Tailwind config to extend.
pnpm add @roy-ui/uiimport { Popover } from '@roy-ui/ui';Click the default 'i' trigger or wire a custom one through renderTrigger. Dismisses on outside-click, Escape, or another trigger click.
The built-in 'i' button. Click to open, click outside or press Escape to dismiss.
<Popover title="How this works" align="left">
Billed monthly. Cancel any time
from your dashboard.
</Popover>Use renderTrigger for any button — bell, avatar, menu icon. The render prop receives isOpen and toggle.
<Popover
align="left"
width="md"
title="Notifications"
renderTrigger={({ isOpen, toggle }) => (
<button
onClick={toggle}
aria-expanded={isOpen}
className={isOpen ? 'is-on' : ''}
>
Notifications
</button>
)}
>
You're all caught up.
</Popover>The panel anchors to the trigger's left or right edge.
<Popover align="left">…</Popover>
<Popover align="right">…</Popover>Width takes 'sm' (280), 'md' (360, default), 'lg' (420), or any number for a custom pixel width.
<Popover width="sm">…</Popover>
<Popover width="lg">…</Popover>
<Popover width={500}>…</Popover>Every visual surface is wired to a CSS variable scoped to .royui-popover. Override the ones you care about — locally on a wrapper or globally on :root.
/* Dark-theme override, scoped to a wrapper */
.dark .royui-popover {
--royui-popover-bg: #18181b;
--royui-popover-border: #2a2a30;
--royui-popover-title: #ffffff;
--royui-popover-body: rgba(255, 255, 255, 0.75);
--royui-popover-trigger-idle: rgba(255, 255, 255, 0.5);
--royui-popover-trigger-hover: #ffffff;
--royui-popover-trigger-bg: rgba(255, 255, 255, 0.08);
}Render a default info trigger or take full control with renderTrigger.
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Optional bolded header inside the panel. |
align | 'left' | 'right' | 'right' | Which edge of the trigger the panel hugs. |
width | 'sm' | 'md' | 'lg' | number | 'md' | Preset (280/360/420) or any pixel value. |
label | string | 'Open menu' | aria-label for the default info trigger. |
defaultOpen | boolean | false | Start in the open state — handy for stories and demos. |
renderTrigger | (api) => ReactNode | — | Replace the default info button. Receives { isOpen, toggle }. |
children | ReactNode | — | Body content rendered inside the panel. |
Honest about what this version doesn't do — saves you from finding out the hard way.
overflow: hidden can clip it. Wrap the trigger in a container with visible overflow.