Tooltip
Source for this preview
---import {TOOLTIP_ARROW} from '../../example-config';---
<article id="tooltip-demo" class="demo-card tooltip-card"> <div class="card-top"> <span class="number">A</span><span class="chip">hover + focus</span> </div> <h3>Signal tooltip</h3> <p> One floating surface wires pointer intent, keyboard focus, dismissal, and descriptive ARIA. </p> <div class="card-action"> <floating-root id="tooltip-root" placement="top" > <floating-reference> <button class="ink-button"> Inspect signal <span aria-hidden="true">↗</span> </button> </floating-reference> <template slot="content"> <div class="tooltip"> Positioned by <b>autoUpdate</b> <floating-arrow class="tooltip-arrow" width={TOOLTIP_ARROW.width} height={TOOLTIP_ARROW.height} ></floating-arrow> </div> </template> </floating-root> </div> <code>hover() → focus() → dismiss()</code></article>
<script> import { FloatingRootElement, arrow, dismiss, flip, focus, hover, offset, role, safePolygon, shift, transformOrigin, } from '@floating-ui-plus/web-components'; import {TOOLTIP_ARROW} from '../../example-config'; import {initializeExample} from './initialize-example';
initializeExample('tooltip', (scope) => { const floating = FloatingRootElement.query(scope, '#tooltip-root'); floating.configure({ middleware: [ offset(TOOLTIP_ARROW.gap), flip(), shift({padding: 12}), transformOrigin({padding: 8}), ], plugins: [ hover({handleClose: safePolygon({buffer: 4})}), focus(), dismiss(), role({role: 'tooltip'}), ], }); floating.on('floatingmount', ({element}) => { const arrowElement = element.querySelector<HTMLElement>('floating-arrow'); floating.middleware = [ offset(TOOLTIP_ARROW.gap), flip(), shift({padding: 12}), ...(arrowElement ? [arrow({element: arrowElement})] : []), transformOrigin({padding: 8}), ]; }); });</script><script setup lang="ts">import { FloatingArrow, FloatingContent, FloatingReference, FloatingRoot, arrow, autoUpdate, dismiss, flip, focus, hover, offset, role, safePolygon, shift, transformOrigin,} from '@floating-ui-plus/vue';import type {Middleware} from '@floating-ui-plus/vue';import {computed, ref, shallowRef} from 'vue';import {TOOLTIP_ARROW} from '../../example-config';
const open = ref(false);const arrowElement = shallowRef<SVGSVGElement | null>(null);const middleware = computed<Middleware[]>(() => [ offset(TOOLTIP_ARROW.gap), flip(), shift({padding: 12}), ...(arrowElement.value ? [arrow({element: arrowElement.value})] : []), transformOrigin({padding: 8}),]);const options = { placement: 'top', middleware, whileElementsMounted: autoUpdate,} as const;const plugins = [ hover({handleClose: safePolygon({buffer: 4})}), focus(), dismiss(), role({role: 'tooltip'}),];</script>
<template> <article class="demo-card tooltip-card"> <div class="card-top"> <span class="number">A</span><span class="chip">hover + focus</span> </div> <h3>Signal tooltip</h3> <p> One floating surface wires pointer intent, keyboard focus, dismissal, and descriptive ARIA. </p> <div class="card-action"> <FloatingRoot v-model:open="open" v-slot="{floating}" :options="options" :plugins="plugins" > <FloatingReference class="ink-button"> Inspect signal <span aria-hidden="true">↗</span> </FloatingReference> <FloatingContent class="tooltip" :data-placement="floating.placement.value" > Positioned by <b>autoUpdate</b> <FloatingArrow class="tooltip-arrow" :width="TOOLTIP_ARROW.width" :height="TOOLTIP_ARROW.height" @element-change="arrowElement = $event" /> </FloatingContent> </FloatingRoot> </div> <code>hover() → focus() → dismiss()</code> </article></template>The tooltip combines pointer and keyboard entry without duplicating open state:
floating.configure({ middleware: [offset(3), flip(), shift({padding: 12})], plugins: [ hover({handleClose: safePolygon({buffer: 4})}), focus(), dismiss(), role({role: 'tooltip'}), ],});safePolygon() keeps the tooltip open while the pointer travels toward the
surface. focus() covers keyboard focus, while dismiss() handles Escape and
focus-out. The demo registers floating-arrow after the template mounts so
the arrow height participates in the visual offset.
<floating-root placement="top" interactions="hover focus dismiss"> <floating-reference><button>Inspect signal</button></floating-reference> <template slot="content"> <div role="tooltip">Positioned by autoUpdate</div> </template></floating-root>Use hover(), focus(), dismiss(), and role({role: 'tooltip'}) in the
Vue adapter with the same behavior contract.
The tooltip is a native Popover surface, so its entry and exit motion uses the
same CSS-only discrete transition described in
Entry and exit animation. Keep the surface rendered
while closed so the browser can finish its display and overlay transitions.