Phoundry UI

Popover

Floating panel anchored to a trigger element with auto-positioning, flip behavior, and backdrop dismissal.

import { Popover } from 'phoundry-ui';

Basic Popover

Show code
<Popover>
  {#snippet trigger(toggle)}
    <Button onclick={toggle}>Open Popover</Button>
  {/snippet}
  <div class="p-3 bg-surface-base rounded-lg border border-border-muted shadow-lg">
    <p class="text-sm">Popover content here</p>
  </div>
</Popover>

Placement Options

Show code
<Popover placement="top">...</Popover>
<Popover placement="bottom-end">...</Popover>
<Popover placement="right">...</Popover>

Offset & flip

offset grows the gap from the anchor; flip=false keeps the requested placement even near viewport edges (may overflow — test on small screens).

Show code
<Popover placement="right-start" offset={12} flip={false}>
  {#snippet trigger(t)}
    <Button onclick={t}>Anchor</Button>
  {/snippet}
  <div class="p-3">…</div>
</Popover>

Non-dismissible backdrop

Outside clicks no longer close the surface — pair with a button that calls toggle() or clears bind:open.

Show code
<Popover dismissible={false} bind:open>
  {#snippet trigger(t)}<Button onclick={t}>Open</Button>{/snippet}
  <div>Use an explicit Close action</div>
</Popover>

Controlled Open

Show code
let open = $state(false);

<Popover bind:open>
  {#snippet trigger(toggle)}
    <Button onclick={toggle}>Toggle</Button>
  {/snippet}
  <div class="p-3">Controlled content</div>
</Popover>
<Button onclick={() => open = false}>Close from outside</Button>

Programmatic overlay

Use when the anchor is not a stable child of the overlay host (selection rects, hover targets). Mount PopoverOverlay once in the root layout; optional content on open() overrides the default child.

Show code
import { getPopoverManager, PopoverOverlay } from 'phoundry-ui';

// In +layout.svelte after setupOverlays():
// <PopoverOverlay>{#snippet children()}…toolbar…{/snippet}</PopoverOverlay>

const popover = getPopoverManager();
popover.open({
  anchor: elementOrDomRect,
  placement: 'top',
  ariaLabel: 'Actions',
});

Props

PropTypeDefaultDescription
open booleanfalseWhether the popover is open. Bindable.
onOpenChange (open: boolean) => voidCallback when open state changes.
placement PopoverPlacement'bottom-start'Positioning relative to the trigger element.
offset number4Gap in pixels between trigger and popover.
flip booleantrueFlip to opposite side if there is not enough space.
dismissible booleantrueClose when clicking outside the popover.
class stringAdditional CSS classes for the floating panel.
trigger Snippet<[toggle: () => void]>Trigger region — receives `toggle`. Optional only if you drive `open` entirely from the outside (unusual).
children requiredSnippetPopover body content.

Usage tips

  • The trigger snippet receives a toggle function — call it on click to open/close.
  • Use bind:open for controlled state — you can open/close from external buttons or logic.
  • With flip: true (default), the popover repositions if it would overflow the viewport.
  • Set dismissible: false to prevent closing on outside clicks (useful for forms).
  • The floating panel uses role="dialog"; focus management is minimal — trap focus inside when hosting complex widgets.
  • Escape closes while open regardless of dismissible (handled on window).