Popover
A floating anchored panel.
Anatomy
<script setup>
import { Popover } from '@shardsui/vue/popover'
</script>
<template>
<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>
</template>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 and closeDelay, 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, detach them: render <Popover.Trigger> wherever it makes sense and connect it to the root with a shared handle from Popover.createHandle().
<script setup>
const demoPopover = Popover.createHandle()
</script>
<template>
<!-- [!code word::handle="demoPopover"] -->
<Popover.Trigger :handle="demoPopover">Trigger</Popover.Trigger>
<!-- [!code word::handle="demoPopover"] -->
<Popover.Root :handle="demoPopover">...</Popover.Root>
</template>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.
<template>
<Popover.Root>
<Popover.Trigger>Trigger 1</Popover.Trigger>
<Popover.Trigger>Trigger 2</Popover.Trigger>
...
</Popover.Root>
</template><script setup>
const demoPopover = Popover.createHandle()
</script>
<template>
<Popover.Trigger :handle="demoPopover">Trigger 1</Popover.Trigger>
<Popover.Trigger :handle="demoPopover">Trigger 2</Popover.Trigger>
<Popover.Root :handle="demoPopover">...</Popover.Root>
</template>When triggers share a popover, each one can hand the root its own data through the payload prop; read it back from the default slot of <Popover.Root> to tailor what the panel shows. Pass a type argument to Popover.createHandle() to type the payload:
<script setup lang="ts">
const demoPopover = Popover.createHandle<{ text: string }>()
</script>
<template>
<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 v-slot="{ payload }" :handle="demoPopover">
<Popover.Portal>
<Popover.Positioner :side-offset="8">
<Popover.Popup>
<Popover.Title>Popover</Popover.Title>
<Popover.Description v-if="payload !== undefined">
This has been opened by {{ payload.text }}
</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
</template>Controlled mode with multiple triggers
To drive the popover from your own state, bind v-model:open on <Popover.Root>. With several triggers, give each one an id and add v-model:trigger-id 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. Its position, its size, and its contents animate independently.
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 — a space-separated horizontal and vertical pair such as right down — so the animation can lean toward the new trigger. 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 entering content, or the sole content when nothing is transitioning.data-previous: the leaving content during a transition.
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.
When using the Viewport, 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 = Popover.createHandle<Payload>()