Skip to content

Popover

A generic floating content container built on Reka UI's Popover primitive.

Usage

Popover's content slot can hold anything - unlike Dropdown (menu items) or Tooltip (hover-only text), it's a blank, interactive panel for things like a quick-edit form or a filter list:

vue
<script setup lang="ts">
const open = ref(false)
const name = ref('')
</script>

<template>
  <SPopover v-model:open="open">
    <SButton variant="outline">
      Edit name
    </SButton>
    <template #content>
      <div class="flex w-64 flex-col gap-3">
        <p class="text-sm font-medium text-[var(--selaras-resolved-text-default)]">
          Display name
        </p>
        <SInput v-model="name" placeholder="Jane Doe" />
        <SButton size="sm" @click="open = false">
          Save
        </SButton>
      </div>
    </template>
  </SPopover>
</template>

v-model:open is optional - omit it and the popover manages its own open state internally, toggled by clicking the trigger.

Side and align

side picks which edge of the trigger the panel opens from (top, right, left, default bottom); align controls how it lines up along that edge (start, center default, end):

vue-html
<SPopover side="right" align="start">...</SPopover>

Arrow

arrow shows a small pointer triangle connecting the panel to its trigger. Pass an object to set its width, height, rounded tip, or minimum clearance from the panel corners (padding):

vue-html
<SPopover arrow>...</SPopover>
<SPopover :arrow="{ width: 16, height: 8, rounded: true, padding: 12 }">...</SPopover>

Popover is non-modal by default - the rest of the page stays reachable, and it isn't hidden from assistive tech. modal="true" traps focus and blocks outside interaction instead, closer to how Modal/Slideover behave:

vue-html
<SPopover modal>...</SPopover>

Intercepting dismissal

Popover doesn't override Reka UI's own defaults (Escape and an outside click both dismiss it), but exposes the underlying events so you can preventDefault() on them:

vue-html
<SPopover
  @escape-key-down="(e) => hasChanges && e.preventDefault()"
  @pointer-down-outside="(e) => hasChanges && e.preventDefault()"
>
  ...
</SPopover>

For the simpler, all-or-nothing case, dismissible="false" disables Escape and outside-click (and, for a non-modal popover, an outside element merely receiving focus) all at once - the raw events still fire, so a consumer can still react, but none of them close it on their own:

vue-html
<SPopover :dismissible="false">...</SPopover>

Return focus on close

Reka returns keyboard focus to the trigger whenever the popover closes, regardless of why it closed - including a purely programmatic open change. That matters for a popover that can open on hover rather than a deliberate click/keypress: focus landing back on a trigger nobody meant to focus can itself count as focus moving "outside" whatever other popover the pointer has since moved on to, closing that one too. returnFocusOnClose="false" disables the behavior for cases like that:

vue-html
<SPopover :return-focus-on-close="false">...</SPopover>

Custom :ui

To see exactly what you'd be overriding - the current default classes for every slot and variant - here's the popover's own theme file:

Theme source "popover" not found

Positioning and portal

positioning configures the panel's side, alignment, offsets, and collision behavior. It overrides the older top-level side and align props when both are supplied. portal defaults to the document body; set it to false to render inline, or pass a CSS selector or HTMLElement as the teleport target. Use ui.content for panel styling and #content for its contents.

vue-html
<SPopover :positioning="{ side: 'top', align: 'end', sideOffset: 8 }" portal="#overlay-root">...</SPopover>

Props

PropTypeDefault
openboolean-
side'top' | 'right' | 'bottom' | 'left''bottom'
align'start' | 'center' | 'end''center'
positioningOverlayPositioningpanel defaults
portalboolean | string | HTMLElementtrue (document body)
modalbooleanfalse
dismissiblebooleantrue
returnFocusOnClosebooleantrue
arrowboolean | RoundedArrowConfigfalse
uiPartial<Record<'content' | 'arrow', string | object>>-

A class passed to <SPopover> is merged into its default-slot trigger element. Use ui.content to style the floating panel.

Events

EventPayloadDescription
update:openbooleanOpen state changed
escapeKeyDownKeyboardEventEscape was pressed - preventDefault() to stop it from closing
pointerDownOutsideEventA pointer went down outside the popover - preventDefault() to stop it from closing
focusOutsideEventA non-modal popover's outside element received focus - preventDefault() to stop it from closing
openAutoFocusEventFires right before focus moves into the popover on open - preventDefault() to skip autofocus for that open (e.g. a hover-triggered open that shouldn't yank focus, while a keyboard-triggered one still should)

Slots

SlotDescription
defaultThe trigger element
contentThe popover's own content
Edit this page