Skip to main content
Version: Next

Popover

import { Popover } from '@editora/ui-react';
// or subpath
import { Popover } from '@editora/ui-react/Popover';

Composition Pattern

Popover uses a composition pattern. Use Popover.Trigger and Popover.Content as named sub-components to declare the trigger and panel body. It now follows the same surface-token pattern as newer components like Card, Dialog, and TransferList, so you can theme the floating panel directly from the root:

<Popover
placement="bottom"
offset={8}
closeOnOutside
closeOnEscape
variant="soft"
tone="brand"
size="md"
elevation="low"
>
<Popover.Trigger>
<Button>Open</Button>
</Popover.Trigger>
<Popover.Content style={{ minWidth: 220 }}>
Popover content with <strong>HTML</strong>.
</Popover.Content>
</Popover>

Sub-components

Sub-componentSlotDescription
Popover.TriggertriggerElement that opens/closes the popover on click
Popover.ContentcontentFloating panel body

Props

PropTypeDefaultDescription
openbooleanControlled open state
placementPopoverPlacement'bottom'Preferred placement of the floating panel
offsetnumber8Distance in px between trigger and panel
shiftbooleantrueShift panel to stay within viewport
flipbooleantrueFlip to opposite side when there is no space
closeOnEscapebooleantrueClose on Escape key
closeOnOutsidebooleantrueClose on click outside
variant'surface' | 'soft' | 'solid' | 'glass' | 'contrast' | 'minimal''surface'Visual surface treatment for the floating panel
tone'brand' | 'neutral' | 'info' | 'success' | 'warning' | 'danger''brand'Accent tone used by themed variants
size'sm' | 'md' | 'lg' | '1' | '2' | '3''md'Panel spacing, type scale, and radius preset
radiusnumber | stringOverrides the panel corner radius
elevation'none' | 'low' | 'high''low'Shadow depth for the floating panel
onOpen() => voidFires when the popover opens
onClose() => voidFires when the popover closes
onOpenChange(detail: { open: boolean }) => voidFires on any open state change

PopoverPlacement

'top' | 'top-start' | 'top-end'
'right' | 'right-start' | 'right-end'
'bottom' | 'bottom-start' | 'bottom-end'
'left' | 'left-start' | 'left-end'

Examples

Rich content panel

<Popover
placement="bottom-start"
offset={8}
closeOnOutside
closeOnEscape
variant="soft"
tone="brand"
size="md"
>
<Popover.Trigger>
<Button>Workspace</Button>
</Popover.Trigger>
<Popover.Content style={{ minWidth: 240 }}>
<strong>Workspace settings</strong>
<p style={{ fontSize: 13, color: '#64748b', marginTop: 4 }}>
Manage members, billing, and integrations.
</p>
</Popover.Content>
</Popover>

Any element as trigger

<Popover placement="top" offset={8} closeOnOutside variant="minimal" tone="neutral" size="sm">
<Popover.Trigger>
<span style={{ cursor: 'pointer', textDecoration: 'underline' }}>What is this?</span>
</Popover.Trigger>
<Popover.Content style={{ maxWidth: 220, fontSize: 13 }}>
Any element can be a trigger — not just buttons.
</Popover.Content>
</Popover>

Surface variants

<Popover variant="glass" tone="info" elevation="high">
<Popover.Trigger>
<Button variant="secondary">Inspect</Button>
</Popover.Trigger>
<Popover.Content style={{ minWidth: 220 }}>
Glass, contrast, soft, solid, and minimal variants all inherit the same
token-driven surface treatment pattern used across the library.
</Popover.Content>
</Popover>

Controlled

const [open, setOpen] = React.useState(false);

<Popover
open={open}
placement="bottom"
offset={8}
onOpenChange={({ open: next }) => setOpen(next)}
closeOnOutside
closeOnEscape
>
<Popover.Trigger>
<Button>Controlled</Button>
</Popover.Trigger>
<Popover.Content style={{ padding: 14, minWidth: 200 }}>
Open state: <strong>{String(open)}</strong>
</Popover.Content>
</Popover>

Notes

  • Popover.Trigger renders a <span slot="trigger"> — wrap any clickable element inside it.
  • Popover.Content renders a <div slot="content"> — suitable for any block content.
  • variant, tone, size, radius, and elevation style the floating panel itself, not the trigger.
  • Focus is returned to the trigger element when the popover closes.
  • Open popovers resync their portaled content when the source content changes dynamically.
  • For a fully headless floating system use the useFloating hook from @editora/ui-react.