npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@floating-ui-plus/web-components

v0.11.2

Published

Floating UI custom elements with interactions, focus management, portals, collections, and search

Readme

@floating-ui-plus/web-components

Headless Custom Elements for floating interfaces, including positioning, interactions, native surfaces, focus, collections, and editable queries. Your application keeps the HTML, classes, and result markup.

Install and register

npm install @floating-ui-plus/web-components
# or: pnpm add @floating-ui-plus/web-components
# or: bun add @floating-ui-plus/web-components

Import once to register all elements. The package also re-exports the framework-neutral APIs from @floating-ui-plus/web.

import '@floating-ui-plus/web-components';

Popover with a native template

Use a root-owned <template slot="content"> for an anchored, conditional surface. The root creates a fresh native Popover while it is open; a portal is not needed for the usual case.

<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">
      Popover content
      <button type="button" data-fup-close>Close</button>
    </section>
  </template>
</floating-root>

The root's baseline accessibility contract uses dialog for an interactive floating surface. Set floating-role only when selecting a different supported semantic role, such as menu or tooltip. region is a landmark role, not a role supported by the package's public floating-role contract.

The root-owned template[slot="content"] is the explicit native Popover composition. A direct <dialog slot="floating"> uses native dialog behavior. Direct slotted non-dialog surfaces also use the Popover API by default; set top-layer="none" for an intentionally positioned/custom surface. floating-role only sets ARIA semantics.

These are browser Web Standards APIs—the Popover API for anchored non-modal surfaces and the native <dialog> element for modal surfaces—not a portal emulation. The browser owns the corresponding top-layer behavior.

Opening a native dialog also applies a ref-counted document scroll lock using CSS overflow: hidden. No touch-event interception is installed by default; add an application-specific touch guard only when targeting a legacy iOS or WebView environment that needs one.

Native dialogs also receive data-fup-safe-area and the CSS variables --fup-safe-area-inset-top, --fup-safe-area-inset-right, --fup-safe-area-inset-bottom, and --fup-safe-area-inset-left. Consume these variables in surface CSS where a notch or home indicator needs extra space; the package does not force a padding policy on custom dialog layouts.

Use a real <dialog slot="floating"> for a modal. The browser then owns the top layer, focus, and inertness. Reserve <floating-portal> for a surface that explicitly must escape a clipping ancestor or render at a custom target.

Configure function values as properties, rather than attributes. configure() keeps root middleware, plugins, and an optional explicit topLayer together; use it when more than one setting changes. Attribute values remain useful only for declarative strings such as placement and interactions.

Attribute policy

Use an id for a unique application element that setup code needs to find, and classes for repeated presentational parts. The package reserves data-* for declarative component APIs and runtime state: data-fup-close, data-search-*, data-active, and data-placement are examples. Avoid application-specific data-* attributes that exist only as selector hooks.

import {
  flip,
  offset,
  shift,
  transformOrigin,
  type FloatingRootElement,
} from '@floating-ui-plus/web-components';

const root = document.querySelector<FloatingRootElement>('floating-root')!;
root.configure({
  middleware: [
    offset(8),
    flip({padding: 12}),
    shift({padding: 12}),
    transformOrigin({padding: 8}),
  ],
});

Use root.close(event, 'click') for an imperative approved close. The root's cancelable floatingbeforeclose event is the synchronous guard point; an application with asynchronous confirmation should finish that work first and then call close().

Native entry and exit animation

Native Popover and <dialog> surfaces close instantly unless their own CSS explicitly includes a non-zero display or overlay transition with allow-discrete. With that CSS present, the element remains mounted and unhidden through the exit transition and is hidden after transitionend.

.floating-panel {
  opacity: 0;
  translate: 0 -0.25rem;
  transform-origin: var(--floating-transform-origin, 50% 0%);
  transition:
    opacity 120ms cubic-bezier(0.23, 1, 0.32, 1),
    translate 120ms cubic-bezier(0.23, 1, 0.32, 1),
    display 120ms allow-discrete,
    overlay 120ms allow-discrete;
}

.floating-panel:popover-open {
  opacity: 1;
  translate: 0 0;
}

@starting-style {
  .floating-panel:popover-open {
    opacity: 0;
    translate: 0 -0.25rem;
  }
}

transformOrigin() writes the CSS variable from the final placement and reference geometry. Keep it after placement-changing middleware; the CSS fallback remains valid when it is omitted.

For a fixed placement, you may omit the middleware and define transform-origin in CSS. CSS alone cannot observe flip() or shift() results, so use transformOrigin() whenever the surface can move to another side or alignment.

Template content stays mounted only while the native exit is running, and a reopen cancels the pending unmount. Use <floating-transition> for a custom surface with top-layer="none". See the entry and exit animation guide for Popover, dialog, reduced-motion, and presence examples.

Editable query

<floating-query> is the general searchable-result primitive. It supplies ARIA combobox/listbox/option behavior and virtual focus by default. Inside a native <dialog>, it automatically uses dialog semantics, so a Command palette does not need semantics="dialog". Set semantics="dialog" for a non-native dialog surface or semantics="none" when the application owns ARIA itself. It does not own a selected value or participate in form submission. Omit option-id-prefix unless an application needs deterministic option IDs: unique IDs are generated by default.

<floating-root placement="bottom-start" interactions="click dismiss">
  <floating-list navigation loop allow-escape>
    <floating-query id="destination-query">
      <floating-reference>
        <input id="destination-input" aria-label="Destination" autocomplete="off" />
      </floating-reference>

      <template slot="content">
        <floating-results>
          <floating-results-status type="idle">
            <p>Start typing.</p>
          </floating-results-status>
          <floating-results-status type="empty">
            <p>No results.</p>
          </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>
import {
  createFuzzySearchSource,
  type FloatingQueryElement,
} from '@floating-ui-plus/web-components';

const destinations = [
  {id: 'seoul', label: 'Seoul'},
  {id: 'tokyo', label: 'Tokyo'},
  {id: 'paris', label: 'Paris'},
];
const query = document.querySelector<FloatingQueryElement>('#destination-query')!;

query.configure<(typeof destinations)[number]>({
  search: {
    source: createFuzzySearchSource(destinations, {keys: [{name: 'label'}]}),
    getItemKey: (item) => item.id,
  },
  getItemLabel: (item) => item.label,
});

query.addEventListener('queryactivate', (event) => {
  const {item} = (event as CustomEvent<{item: (typeof destinations)[number]}>).detail;
  document.querySelector<HTMLInputElement>('#destination-input')!.value = item.label;
});

Place a standard aria-live region inside <floating-query> when status copy is needed; it is discovered automatically. status-selector remains available for an intentionally different target.

configure() accepts either SearchOptions (the element owns the resulting SearchController) or an existing SearchController. For server search, provide an application-owned async source; the package does not choose your transport or cache library. Use query.setQuery() for programmatic changes and query.search?.loadMore() for cursor pagination.

External contenteditable and editor queries

Configure externalInput to use an editor outside the query's DOM subtree. Query extraction, composition state, and document updates remain editor-owned:

query.configure({
  externalInput: editorElement,
  search: {source, getItemKey: (item) => item.id},
  getItemLabel: (item) => item.label,
});
await query.updateComplete;

// In the editor update hook:
query.setExternalQuery(queryText, {composing: editorIsComposing});

// In the editor's own keyboard hook, after its IME guards:
const handled = !editorIsComposing && query.handleKeyDown(event);

Keep <floating-query> inside <floating-list navigation loop virtual>. External mode enables the list's manualKeyboard mode while bound, so FUP does not intercept editor keydown before ProseMirror processes composition. query.handleKeyDown(event) handles list navigation and Enter activation and returns a boolean without redispatching a synthetic event. Escape, Tab, document range validation, and transactions stay in the application.

Standalone lists also expose list.handleKeyDown(event); set the manual-keyboard attribute to leave native event ordering to the caller. The editor stays the root's real reference, while root.setPositionReference() can track its caret separately. Removing/rebinding the query restores its external loading/active-option attributes and the list's keyboard mode.

The demo's /fixtures/editor-query route tests this contract with real Tiptap. Its Chrome and WebKit E2E covers typing, original-event navigation, portal clicks, scrolling, composition event sequences, dismissal, and remount cleanup. Synthetic composition events verify the integration contract; they do not automate a platform's native IME candidate window.

See the editor Query guide for the external input API, an editor adapter example, and the guards to keep when migrating a custom slash menu.

Closing contract

data-fup-close remains a convenient declarative close control. For imperative closing, call the owning root:

root.close(event, 'click');

Every close path—close(), data-fup-close, dismiss interactions, and native Popover/dialog close events—first emits the cancelable, bubbling and composed floatingbeforeclose event. Its detail is {sourceEvent, reason}.

root.addEventListener('floatingbeforeclose', (event) => {
  sendCloseMetric(event.detail.reason);
  if (!canClose()) event.preventDefault();
});

When prevented, the surface stays open and openchange is not emitted. openchange only represents approved state changes and carries {open, sourceEvent, reason}. Asynchronous approval is application-owned: complete the work, then call root.close().

Transient presence stacks

floating-presence-stack renders timed notifications or other transient surfaces from one native content template. It is headless visually, but owns the core host box, bounded controller, opt-in pause binding, exit presence, Top Layer lifecycle, and final clone removal. Clones default to native popover="manual"; set top-layer="none" for an in-flow stack. The application owns ARIA, focus-entry keys, layout, and styling.

<floating-presence-stack
  id="notices"
  tabindex="-1"
  pause-on="pointer focus"
>
  <template slot="content">
    <article role="status">
      <strong data-presence-text="title"></strong>
      <button type="button" data-presence-close>Dismiss</button>
    </article>
  </template>
</floating-presence-stack>

<script type="module">
  const notices = document.querySelector('#notices');
  notices.add({title: 'Saved'});
</script>

Use configure() when several runtime values belong to one application configuration object. The equivalent limit, timeout, exit-duration, top-layer, pause-on, and resume-delay attributes remain available for static HTML. Code options take precedence over matching attributes, and options is also an assignable property for custom-element bindings. Leave exit-duration unset to unmount after the consumer CSS discrete display / overlay length.

data-presence-text accepts a value path plus $id, $index, and $remaining. The element writes data-status, data-presence-id, data-presence-index, data-presence-overflowed, and --floating-presence-index on the first template root, and --floating-presence-count on the host. Mark a nested surface with data-presence-item only when a multi-root template needs an explicit target. The host reflects controller state with data-presence-paused and data-presence-open, and emits presencechange with {snapshot}. Set pause-on="pointer focus" or call pause(reason) and resume(reason) from the interaction policy appropriate to the application. Size --floating-presence-hit-span to the current expanded stack (card height plus a fixed row times count - 1) so the paused host box matches the cards, not a maximum cap.

Like floating-root, the stack reads its surface from template[slot="content"]. The default Top Layer is Popover. Use none for an in-flow surface; Dialog remains available for genuinely modal content but is not appropriate for Toasts.

Set timeout="0" to keep entries open until close(id) is called.

Elements

| Element | Purpose | | --- | --- | | floating-root, floating-reference, floating-item | Reference, surface, placement, and interaction controller | | floating-content, floating-portal, floating-overlay | Surface mounting, custom targets, and overlay/scroll lock | | floating-focus-manager, floating-transition, floating-arrow | Focus behavior, presence state, and arrow geometry | | floating-presence-stack | Declarative timed stack rendering, native Top Layer, pause/resume, exit presence, and removal | | floating-tree, floating-node | Nested floating surfaces and parent focus restoration | | floating-list, floating-list-item | Ordered items, typeahead, roving or virtual focus | | floating-query, floating-results | Query input and rendered search phases/results | | floating-results-status, floating-results-item, floating-results-more | Composable result phase, item, and pagination parts | | floating-composite, floating-composite-item | Composite/grid roving focus; item-selector can register existing controls | | floating-delay-group, next-floating-delay-group | Shared hover/focus delays |

<floating-results> is the preferred phase renderer. The previous <floating-search> name remains registered as a deprecated compatibility alias; migrate markup when convenient. The floating-results-* parts replace the repeated native template[data-search-*] wrapper pattern. Keep data-search-text and data-search-load-more inside a part when binding a result field or load-more button, because those are declarative data bindings.

floating-combobox compatibility

<floating-combobox> is deprecated but fully supported for existing native form workflows. Unlike <floating-query>, it owns selectedItem, input-label replacement, getItemValue(), name, required, and form reset behavior. Use it only when those form-associated selected-value semantics are required.

Documentation