Popover
A floating anchored panel.
Anatomy
<script>
import { Popover } from '@shardsui/svelte/popover'
</script>
<Popover.Root>
<Popover.Trigger />
<Popover.Portal>
<Popover.Backdrop />
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Viewport>
<Popover.Title />
<Popover.Description />
<Popover.Close />
</Popover.Viewport>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>Examples
Opening on hover
Set openOnHover on the trigger to open the popover on hover as well as on click. Tune the timing with delay (how long the pointer must rest before it opens) and closeDelay (how long it lingers after the pointer leaves), both in milliseconds.
Detached triggers
By default the trigger sits inside <Popover.Root>, right beside the content it opens, as at the top of this page.
When the two can't live together in the markup (say the button belongs in a toolbar but the panel elsewhere), detach them: render <Popover.Trigger> wherever it makes sense and connect it to the root with a shared handle from new Popover.Handle().
<script>
const demoPopover = new Popover.Handle()
</script>
<Popover.Trigger handle={demoPopover}>Trigger</Popover.Trigger>
<Popover.Root handle={demoPopover}>...</Popover.Root>Multiple triggers
One popover can answer to several triggers: drop multiple <Popover.Trigger> elements inside a single <Popover.Root>, or give the same handle to any number of detached triggers.
<Popover.Root>
<Popover.Trigger>Trigger 1</Popover.Trigger>
<Popover.Trigger>Trigger 2</Popover.Trigger>
...
</Popover.Root><script>
const demoPopover = new Popover.Handle()
</script>
<Popover.Trigger handle={demoPopover}>Trigger 1</Popover.Trigger>
<Popover.Trigger handle={demoPopover}>Trigger 2</Popover.Trigger>
<Popover.Root handle={demoPopover}>...</Popover.Root>When triggers share a popover, each one can hand the root its own data through the payload prop; read it back from the children snippet on <Popover.Root> to tailor what the panel shows. Pass a type argument to new Popover.Handle() to type the payload:
<script>
const demoPopover = new Popover.Handle<{ text: string }>()
</script>
<Popover.Trigger handle={demoPopover} payload={{ text: 'Trigger 1' }}>Trigger 1</Popover.Trigger>
<Popover.Trigger handle={demoPopover} payload={{ text: 'Trigger 2' }}>Trigger 2</Popover.Trigger>
<Popover.Root handle={demoPopover}>
{#snippet children({ payload })}
<Popover.Portal>
<Popover.Positioner sideOffset={8}>
<Popover.Popup>
<Popover.Title>Popover</Popover.Title>
{#if payload !== undefined}
<Popover.Description>
This has been opened by {payload.text}
</Popover.Description>
{/if}
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
{/snippet}
</Popover.Root>Controlled mode with multiple triggers
To drive the popover from your own state — a button elsewhere on the page, a keyboard shortcut — bind open and handle onOpenChange on <Popover.Root>. With several triggers, give each one an id and add bind:triggerId to <Popover.Root>: each trigger publishes its own id when it opens the popover, and setting triggerId yourself anchors the popover to that trigger.
Animating the Popover
When one popover serves several triggers, it can glide from one to the next instead of snapping. Three things animate independently: its position, its size, and its contents.
Position and Size
Position lives on the Positioner, so transition its left, right, top, and bottom. Size lives on the Popup, so transition its width and height.
Content
The content itself can cross-fade when the active trigger changes. Wrap it in <Popover.Viewport>, which notices the switch and exposes a data-activation-direction attribute so the animation can lean toward the new trigger. Its value is a space-separated set of up to two tokens, one per axis — left or right for the horizontal axis, up or down for the vertical one (for example, right down). Match a single token with the ~= attribute selector, such as [data-activation-direction~='right'].
During a transition the viewport keeps both the incoming and outgoing content mounted, each in its own wrapper:
data-current— the content coming in, or the only content when nothing is transitioning. It also carriesdata-starting-stylewhile it animates in.data-previous— the content on its way out. It also carriesdata-ending-stylewhile it animates out.
Target these to write the enter and exit animations.
API reference
Root
Groups all parts of the popover. Doesn't render its own HTML element.
Trigger
A button that opens the popover.
Renders a <button> element.
Backdrop
An overlay displayed beneath the popup.
Renders a <div> element.
Portal
A portal that moves the popup out to <body>, clear of ancestor clipping and stacking.
Renders a <div> element.
Positioner
Positions the popover against the trigger.
Renders a <div> element.
Popup
A container for the popover contents.
Renders a <div> element.
Arrow
Displays an element positioned against the anchor.
Renders a <div> element.
Title
A heading that labels the popover.
Renders an <h2> element.
Description
A paragraph with additional information about the popover.
Renders a <p> element.
Close
A button that closes the popover.
Renders a <button> element.
Viewport
A viewport for displaying content transitions.
This component is only required if one popup can be opened by multiple triggers, its content
changes based on the trigger, and switching between them is animated.
Renders a <div> element.
The Viewport is optional — reach for it only when a single popup is opened by multiple triggers, its content differs per trigger, and the switch between them is animated. When used, set width: var(--positioner-width) and height: var(--positioner-height) on the Positioner so its box is frozen to the measured size during the transition; otherwise content-driven resizing can make the popup thrash or flip to another side.
Handle
Connects a <Popover.Root> with detached <Popover.Trigger> components, and controls the popover imperatively. Pass a type argument to type the payload.
const popover = new Popover.Handle<Payload>()