Popover
A floating panel anchored to a trigger element. Supports focus management, dismissal, positioning via @floating-ui, and an optional overlay.
Anatomy
<Popover.Provider bind:open>
<Popover.Trigger>Toggle</Popover.Trigger>
<Popover.Content>
<Popover.Arrow />
<Popover.Title>Details</Popover.Title>
<Popover.Description>Context for the floating panel.</Popover.Description>
<p>Popover content here.</p>
<Popover.Close>Close</Popover.Close>
</Popover.Content>
</Popover.Provider>
Content renders inline at the position the consumer authored it — there is no automatic Portal. The Floating layer uses position: fixed (configurable via strategy), which escapes most ancestor stacking contexts but not ancestors that establish a containing block for fixed elements (transform, filter, perspective, will-change, contain, backdrop-filter). If your popover is clipped or mis-stacked, place the Provider outside such ancestors. An optional Anchor part allows positioning relative to an element other than the trigger.
Parts
| Part |
Element |
Description |
Provider |
none |
Root context. Manages open state. |
Trigger |
<button> |
Toggles the popover on click. |
Content |
<div> |
Floating panel. Focus scope, dismissal, positioning integrated. |
Arrow |
<div> |
Floating arrow pointing toward the trigger. |
Title |
<div> |
Optional accessible name for Content. |
Description |
<div> |
Optional accessible description for Content. |
Close |
<button> |
Closes the popover on click. |
Anchor |
<div> |
Alternative anchor point for positioning. |
Overlay |
<div> |
Optional backdrop behind the popover. |
ARIA
| Part |
Attribute |
Value |
| Trigger |
aria-haspopup |
dialog |
| Trigger |
aria-expanded |
true | false |
| Trigger |
aria-controls |
ID of Content |
| Content |
role |
dialog |
| Content |
aria-labelledby |
Title id when present, otherwise Trigger id |
| Content |
aria-describedby |
Description id when present |
| Content |
data-side |
top | right | bottom | left |
| Content |
data-align |
start | center | end |
| Title |
role |
heading |
| Title |
aria-level |
Consumer-controlled level, defaults to 2 |
| Close |
aria-label |
Translated label |
| Close |
type |
button |
| Overlay |
aria-hidden |
true |
Data Attributes
| Part |
Attribute |
Values |
| Trigger |
data-popover-trigger |
Always present |
| Trigger |
data-state |
open | closed |
| Content |
data-popover-content |
Always present |
| Content |
data-state |
open | closed |
| Content |
data-side |
top | right | bottom | left |
| Content |
data-align |
start | center | end |
| Content |
data-starting-style |
Present during open animation |
| Content |
data-ending-style |
Present during close animation |
| Title |
data-popover-title |
Always present |
| Description |
data-popover-description |
Always present |
| Overlay |
data-popover-overlay |
Always present |
| Overlay |
data-state |
open | closed |
CSS Variables
| Variable |
Part |
Description |
--floating-transform-origin |
Content |
Transform origin based on placement |
--floating-available-width |
Content |
Available width before collision |
--floating-available-height |
Content |
Available height before collision |
--floating-anchor-width |
Content |
Width of the anchor element |
--floating-anchor-height |
Content |
Height of the anchor element |
Keyboard
| Key |
Action |
Enter / Space |
Toggle the popover (on trigger) |
Escape |
Close the popover (configurable) |
Tab |
Cycle focus within the popover (when focus is trapped) |
Focus moves to the first focusable element on open. On close, focus returns to the trigger.
Usage
Basic
<script>
import { Popover } from '$soma/components';
let open = $state(false);
</script>
<Popover.Provider bind:open>
<Popover.Trigger>Info</Popover.Trigger>
<Popover.Content>
<Popover.Arrow />
<Popover.Title>More information</Popover.Title>
<Popover.Description>Short context for the details below.</Popover.Description>
<p>Additional information here.</p>
<Popover.Close>Got it</Popover.Close>
</Popover.Content>
</Popover.Provider>
Custom positioning
<Popover.Content side="right" align="start" sideOffset={8}>
<!-- Opens to the right, aligned to the top of the trigger -->
</Popover.Content>
With overlay
<Popover.Provider bind:open>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Overlay />
<Popover.Content>Content</Popover.Content>
</Popover.Provider>
Custom anchor
<Popover.Provider bind:open>
<Popover.Anchor>
<span>Positioned relative to this element</span>
</Popover.Anchor>
<Popover.Trigger>Open</Popover.Trigger>
<Popover.Content>Content</Popover.Content>
</Popover.Provider>