Menu

Change language

GitHub repo

Popover

popoverui

Click-triggered floating panel anchored to a trigger element. Supports optional localStorage persistence and configurable dismissal (click-outside, escape, manual). Hand-rolled runes positioning with viewport clamping.

When to use which

Decision Guide

Dialogs capture immediate user focus for critical actions. Sheets slide in from screen edges for deep secondary forms, while Popovers provide lightweight contextual cards.

ComponentRole
Dialog

Critical confirmations, destructive warnings, or focused single-purpose tasks requiring user resolution.

Modal Window
Sheet

Complex edit forms, filter panes, or shopping carts that retain underlying page context.

Off-Canvas Slide-Over
Drawer

Mobile bottom sheets, touch-friendly sliding panels, or secondary inspection trays.

Edge Panel / Bottom Sheet
Popovercurrent

Small contextual dropdowns, color pickers, date pickers, or inline configuration flyouts.

Contextual Floating Card
  • Urgent confirmation or focused prompt → Dialog (view)
  • Side panel for deep form editing → Sheet (view)
  • Mobile bottom sheet or gesture tray → Drawer (view)
  • Anchored contextual flyout or controls → Popover

Installation

$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/popover.json
Manifest:https://uipkge.dev/r/svelte/popover.jsonInstalls to:src/lib/components/ui/popover/

Examples

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
open

Controlled open state (`bind:open`). Omit for uncontrolled.

boolean—optional
defaultOpenbooleanfalseoptional
modal

Modal popovers trap Tab focus while open. Default false.

booleanfalseoptional
persist

Persist open state to localStorage. `true` auto-keys, a string sets the key.

string | booleanfalseoptional
closeBehaviorPopoverCloseBehaviorautooptional
onOpenChange(open: boolean) => void—optional
childrenSnippet—optional

Schema

Type aliases from this item's source — use them to shape the data you pass in.

PopoverContextValue
interface PopoverContextValue {
  id: string
  contentId: string
  triggerId: string
  isOpen: () => boolean
  setOpen: (open: boolean) => void
  toggle: () => void
  isModal: () => boolean
  getCloseBehavior: () => PopoverCloseBehavior
  getTriggerEl: () => HTMLElement | null
  getAnchorEl: () => HTMLElement | null
  registerTrigger: (el: HTMLElement) => void
  unregisterTrigger: (el: HTMLElement) => void
  registerAnchor: (el: HTMLElement) => void
  unregisterAnchor: (el: HTMLElement) => void
}

Used by

Files installed (6)

  • src/lib/components/ui/popover/Popover.svelte2.8 kB
    <script lang="ts" module>
      import type { Snippet } from 'svelte'
      import type { PopoverCloseBehavior } from './context'
    
      export interface PopoverProps {
        /** Controlled open state (`bind:open`). Omit for uncontrolled. */
        open?: boolean
        defaultOpen?: boolean
        /** Modal popovers trap Tab focus while open. Default false. */
        modal?: boolean
        /** Persist open state to localStorage. `true` auto-keys, a string sets the key. */
        persist?: string | boolean
        closeBehavior?: PopoverCloseBehavior
        onOpenChange?: (open: boolean) => void
        children?: Snippet
      }
    </script>
    
    <script lang="ts">
      import { setContext } from 'svelte'
      import { POPOVER_CONTEXT_KEY, type PopoverContextValue } from './context'
    
      let {
        defaultOpen = false,
        open = $bindable(defaultOpen),
        modal = false,
        persist = false,
        closeBehavior = 'auto',
        onOpenChange,
        children,
      }: PopoverProps = $props()
    
      const autoId = $props.id()
    
      const storageKey = $derived(
        persist === false || persist === undefined
          ? null
          : persist === true
            ? `uipkge-popover-${autoId}`
            : persist,
      )
    
      // Read the persisted value on the first client pass, then write on change.
      // Runs only in the browser ($effect never fires during SSR), matching the
      // Vue twin's onMounted read (no hydration mismatch from a server read).
      let didInitPersist = false
      $effect(() => {
        const key = storageKey
        if (key && typeof localStorage !== 'undefined') {
          if (!didInitPersist) {
            didInitPersist = true
            if (localStorage.getItem(key) === '1') {
              setOpen(true)
              return
            }
          }
          if (open) localStorage.setItem(key, '1')
          else localStorage.removeItem(key)
        }
      })
    
      function setOpen(value: boolean) {
        open = value
        onOpenChange?.(value)
      }
    
      let triggerEl: HTMLElement | null = null
      let anchorEl: HTMLElement | null = null
    
      const ctx: PopoverContextValue = {
        id: autoId,
        contentId: `${autoId}-content`,
        triggerId: `${autoId}-trigger`,
        isOpen: () => open,
        setOpen,
        toggle: () => setOpen(!open),
        isModal: () => modal,
        getCloseBehavior: () => closeBehavior,
        getTriggerEl: () => triggerEl,
        getAnchorEl: () => anchorEl,
        // First registration wins: a Close trigger nested inside the content must
        // not steal the positioning anchor from the outer trigger.
        registerTrigger: (el) => {
          if (!triggerEl) triggerEl = el
        },
        unregisterTrigger: (el) => {
          if (triggerEl === el) triggerEl = null
        },
        registerAnchor: (el) => {
          if (!anchorEl) anchorEl = el
        },
        unregisterAnchor: (el) => {
          if (anchorEl === el) anchorEl = null
        },
      }
      setContext(POPOVER_CONTEXT_KEY, ctx)
    </script>
    
    <span data-uipkge="" data-slot="popover" class="contents">
      {@render children?.()}
    </span>
    
  • src/lib/components/ui/popover/PopoverAnchor.svelte1 kB
  • src/lib/components/ui/popover/PopoverContent.svelte7.6 kB
  • src/lib/components/ui/popover/PopoverTrigger.svelte2.4 kB
  • src/lib/components/ui/popover/context.ts0.8 kB
  • src/lib/components/ui/popover/index.ts0.4 kB

Raw manifest:https://uipkge.dev/r/svelte/popover.json