Popover
Source for this preview
<article id="popover-demo" class="demo-card popover-card"> <div class="card-top"> <span class="number">B</span><span class="chip">named slots</span> </div> <h3>Anchored popover</h3> <p>The trigger and panel are composed declaratively through named slots.</p> <div class="card-action"> <floating-root id="popover-root" interactions="click dismiss" placement="bottom-start" > <floating-reference> <button class="coral-button"> Open coordinates <span aria-hidden="true">+</span> </button> </floating-reference> <template slot="content"> <div class="popover-panel"> <span class="panel-kicker">NATIVE POPOVER / 42.8°</span> <strong>Popover, still connected.</strong> <p>ARIA and outside-press behavior are owned by the component.</p> <button class="text-button" data-fup-close>Close panel</button> </div> </template> </floating-root> </div> <code><template slot="content"> + native popover</code></article>
<script> import { FloatingRootElement, flip, offset, shift, transformOrigin, } from '@floating-ui-plus/web-components'; import {initializeExample} from './initialize-example';
initializeExample('popover', (scope) => { const floating = FloatingRootElement.query(scope, '#popover-root'); floating.configure({ middleware: [ offset(12), flip(), shift({padding: 18}), transformOrigin({padding: 12}), ], }); });</script><script setup lang="ts">import { FloatingClose, FloatingContent, FloatingReference, FloatingRoot, autoUpdate, click, dismiss, flip, offset, shift, transformOrigin,} from '@floating-ui-plus/vue';import {ref} from 'vue';
const open = ref(false);const options = { placement: 'bottom-start', middleware: [ offset(12), flip(), shift({padding: 18}), transformOrigin({padding: 12}), ], whileElementsMounted: autoUpdate,} as const;const plugins = [click(), dismiss()];</script>
<template> <article class="vue-demo-card bg-vue-sky"> <div class="vue-card-top"> <span class="vue-number">B</span><span>native top layer</span> </div> <h3>Anchored popover</h3> <p> The browser lifts the panel above the page while Vue keeps its normal component tree. </p> <div class="mt-auto pt-7"> <FloatingRoot v-model:open="open" v-slot="{floating}" :options="options" :plugins="plugins" > <FloatingReference class="vue-button vue-button-sky"> Open coordinates <span>+</span> </FloatingReference> <FloatingContent as="section" class="popover-panel" :data-placement="floating.placement.value" > <span class="panel-kicker">NATIVE TOP LAYER / 42.8°</span> <strong>Popover, still connected.</strong> <p>Outside press and ARIA work without a Teleport.</p> <FloatingClose class="text-button">Close panel</FloatingClose> </FloatingContent> </FloatingRoot> </div> <code>FloatingContent → native popover</code> </article></template>The trigger and panel remain in one root. The template is inert while closed and becomes a native Popover when opened:
<floating-root interactions="click dismiss" placement="bottom-start"> <floating-reference><button>Open coordinates</button></floating-reference> <template slot="content"> <section aria-label="Coordinates"> Popover, still connected. <button data-fup-close>Close panel</button> </section> </template></floating-root>This is the browser’s standard Popover API in action. The demo keeps the surface in the root tree so the platform owns top-layer stacking and light dismissal; it does not emulate a popover with a portal.
The demo uses offset(12), flip(), and shift({padding: 18}). The Vue
version receives the same native Popover behavior by default. Use a portal
only when the panel must escape a clipping ancestor; it is not required for
this composition.
The panel also demonstrates CSS-only entry and exit motion with
:popover-open, @starting-style, and discrete display/overlay
transitions. See Entry and exit animation for the
complete pattern and its rendering requirements.