Skip to main content
Urbicon UI

Popover

Floating content panel anchored to a trigger.

Playground

Choose an option…
Size Style variant
Offset
4
<script lang="ts">
  import { ChevronDownIcon, Popover } from '@urbicon-ui/blocks';
</script>

<Popover
  offsetDistance={4}
  placement="bottom-start"
>
  {#snippet trigger()}
    <div
      class="bg-surface-base border-border-default hover:border-border-emphasis flex w-80 cursor-pointer items-center justify-between rounded-lg border px-3 py-2.5 text-sm transition-colors"
    >
      <span class="text-text-secondary">Choose an option…</span>
      <ChevronDownIcon class="text-text-tertiary h-4 w-4" />
    </div>
  {/snippet}

  <div class="divide-border-subtle divide-y">
    {#each ['Design tokens', 'Component variants', 'Documentation'] as option (option)}
      <div
        class="text-text-primary hover:bg-surface-hover cursor-pointer px-3 py-2 transition-colors first:rounded-t-md last:rounded-b-md"
      >
        {option}
      </div>
    {/each}
  </div>
</Popover>

01 Purpose

Popover is a floating panel anchored to a trigger. Reach for it for contextual surfaces like pickers, inline help or a profile panel that sit next to the element the user acted on without blocking the page. You supply the trigger button and the panel content. The popover positions itself, flips when space runs out, and opens and closes on its own.

Reach forWhen you needFocus
Popover (this)A contextual panel by the trigger: a picker, inline help, a profile panel.Non-modal; focus flows past.
TooltipA short hover/focus description tied to aria-describedby.Non-interactive.
MenuA list of selectable actions with arrow-key navigation.Roving focus.
Dialog / DrawerA blocking, modal flow: confirmation, form, edge sheet.Focus trapped.

Inside flowing text: a trigger that sits in a paragraph needs inline, which makes its wrapper a <span> so a <div> panel cannot break the surrounding <p>.

02 Examples

Rich content

A popover carries structured content a Menu cannot. You provide the trigger button in the trigger snippet, and the panel is whatever markup you put inside.
<Popover placement="bottom-end">
  {#snippet trigger()}
    <button aria-label="Open user menu">JD</button>
  {/snippet}
  <div class="w-64">
    <!-- profile header, actions, sign-out -->
  </div>
</Popover>

Controlled open state

bind:open lets outside code read and drive the panel.
<script>
  let open = $state(false);
</script>

<Popover bind:open>
  {#snippet trigger()}
    <Button>{open ? 'Viewing' : 'View'} status</Button>
  {/snippet}
  <div class="p-3">...</div>
  <Button onclick={() => (open = false)}>Dismiss</Button>
</Popover>

03 Customization

Primary-framed callout

One class frames the panel in the primary token, a border-primary edge and a soft primary ring, to mark a branded or high-priority surface. Only the border and ring change. The fill, radius and motion stay as they are.
<Popover class="border-primary ring-primary/35 ring-2">
  {#snippet trigger()}
    <Button intent="primary">Upgrade plan</Button>
  {/snippet}
  <div class="w-60 p-1">
    <div class="text-primary-emphasis text-sm font-semibold">Go Premium</div>
    <p class="text-text-secondary mt-1 mb-3 text-xs leading-relaxed">
      Unlock advanced analytics, priority support and unlimited projects.
    </p>
    <Button intent="primary" size="sm" class="w-full">Start free trial</Button>
  </div>
</Popover>

This is one of five ways to restyle a block. See Customization for class, slotClasses, unstyled, preset and provider-level overrides.

04 Accessibility

ARIA attributes

The trigger's first interactive element receives aria-haspopup="dialog" and aria-expanded reflecting the open state. The floating panel carries role="dialog" by default. aria-modal is passed through as an attribute only. The popover never traps focus, so leave it unset and reach for Dialog when a flow is genuinely modal.

Keyboard

Enter / Space toggle the popover when the trigger is focused. Escape closes it and returns focus to the trigger. Tab moves through the focusable content inside.

Focus & dismissal

Clicking outside dismisses the popover automatically. Set closeOnClickOutside to false to pin it open until you toggle open yourself. For a flow that must hold focus until dismissed, use Dialog or ConfirmDialog instead.

Motion & reduced motion

The panel fades and scales in over about 150 ms. Override the timing per instance with transitionDuration / transitionEasing, and under prefers-reduced-motion the motion collapses to near-instant.

05 API Reference

26 props
26 props 1 required
Prop
Type
Default
Description

06 Types

Local type definitions used by this component.

5 types
Name
Kind
Category
Used by
Description

07 Installation

Import

import { Popover } from '@urbicon-ui/blocks';