Skip to content
llms.txt
llms.txt

Web Components and Vue

The Web Components and Vue packages expose the same interaction model. Choose the tab that matches your renderer; the close contract, native top-layer rules, and search lifecycle remain the same. Detailed component-family guidance lives in the Components section.

Both adapters provide a reference, a floating surface, an open model, interaction plugins, and an optional focus manager. The application owns labels, classes, ARIA names, transition styling, and result markup; the adapter owns bindings, lifecycle state, and the native surface integration.

When you opt into a native surface, Floating UI Plus uses the browser’s Web Standards APIs: the Popover API for anchored non-modal surfaces and the native <dialog> element for modal surfaces.

Register the elements once, then compose a root-owned template:

<floating-root
placement="bottom-start"
interactions="click dismiss"
>
<floating-reference>
<button type="button">Open settings</button>
</floating-reference>
<template slot="content">
<section aria-label="Settings">
Settings
<button type="button" data-fup-close>Close</button>
</section>
</template>
</floating-root>
import "@floating-ui-plus/web-components";
import { flip, offset, shift } from "@floating-ui-plus/web";
import type {
FloatingOpenChangeDetail,
FloatingRootElement,
} from "@floating-ui-plus/web-components";
const root = document.querySelector<FloatingRootElement>("floating-root")!;
root.middleware = [offset(8), flip(), shift({ padding: 12 })];
root.addEventListener("openchange", (event) => {
const { detail } = event as CustomEvent<FloatingOpenChangeDetail>;
console.log(detail.open, detail.reason, detail.sourceEvent);
});

Use a JavaScript property for functions and objects such as middleware, plugins, search sources, or virtual references. Attributes are for strings and booleans.

Every floating surface starts with dialog semantics. Use floating-role or role() only when a surface needs a different ARIA contract, such as menu, tooltip, or combobox; they do not choose a native top-layer surface. Web Components root-owned templates and ordinary slotted surfaces, as well as Vue FloatingContent, use the Popover API by default. Use top-layer="none" for an intentionally positioned/custom surface and as="dialog" for a native dialog.

For an anchored non-modal surface, keep the content in the same tree and let the browser use Popover. For a modal, use a real dialog element:

<floating-root interactions="click dismiss">
<floating-reference><button>Open account</button></floating-reference>
<dialog slot="floating" aria-labelledby="account-title">
<h2 id="account-title">Account settings</h2>
<button data-fup-close>Close</button>
</dialog>
</floating-root>

Do not portal the same native surface. Add floating-portal or FloatingPortal only when the element must move to a body-level target, such as escaping a clipping ancestor. For a custom non-native modal, compose a portal with an overlay and focus manager.

dismiss() detects outside press, Escape, reference press, and ancestor scroll. It is not the close API. close()/FloatingClose are explicit close commands, and every close path runs the same synchronous before-close guard.

root.addEventListener("floatingbeforeclose", (event) => {
if (hasUnsavedChanges()) {
event.preventDefault();
return;
}
saveCloseMetric(event.detail.reason);
});
// Imperative close; keep the source event when one exists.
root.close(clickEvent, "click");

preventDefault() keeps open unchanged and suppresses openchange.

The Web Components and Vue search adapters consume the same phases: idle, loading, error, empty, and results. Use fuzzy search for a local collection and an async source for a server-backed list. The controller owns debounce, cancellation, stale-response protection, de-duplication, and cursor pagination; each renderer owns the phase markup.

<floating-root placement="bottom-start">
<floating-list navigation loop allow-escape>
<floating-query>
<floating-reference>
<input aria-label="Destination" />
</floating-reference>
<template slot="content">
<floating-results>
<floating-results-status type="loading">Searching…</floating-results-status>
<floating-results-status type="empty">No matches</floating-results-status>
<floating-results-item>
<floating-list-item>
<button type="button" data-search-text="label"></button>
</floating-list-item>
</floating-results-item>
</floating-results>
</template>
</floating-query>
</floating-list>
</floating-root>

Configure search, getItemLabel, and getItemKey as properties in JavaScript. The default semantics are ARIA combobox; use semantics="dialog" for a command palette or semantics="none" when the renderer owns ARIA.

For a full local fuzzy source implementation, see the Combobox fuzzy-search demo, and for an application-owned async source see the server-search demo. For complete composition examples, see the usage recipes.