Middleware
Source for this preview
---import MiddlewareTitle from './MiddlewareTitle.astro';import { MIDDLEWARE_ARROW, MIDDLEWARE_EXAMPLES,} from '../../middleware-registry';
const [offsetExample, shiftExample, flipExample, arrowExample, sizeExample, autoExample, hideExample, inlineExample] = MIDDLEWARE_EXAMPLES;---
<section id="middleware-demo" class="middleware-view"> <section class="middleware-grid" aria-label="Floating UI middleware examples"> <article id="middleware-offset" class="middleware-card"> <MiddlewareTitle id={offsetExample.id} /> <p>{offsetExample.description}</p> <div class="mw-comparison" aria-label="Offset comparison"> <div class="mw-static-stage"> <span class="mw-demo-label">0px</span> <floating-root id="middleware-offset-0-root" open placement="top"> <button slot="reference" class="mw-reference mw-reference-static"> Reference </button> <template slot="content"> <div class="mw-panel mw-panel-cyan">Floating</div> </template> </floating-root> </div> <div class="mw-static-stage"> <span class="mw-demo-label">10px</span> <floating-root id="middleware-offset-10-root" open placement="top"> <button slot="reference" class="mw-reference mw-reference-static"> Reference </button> <template slot="content"> <div class="mw-panel mw-panel-cyan">Floating</div> </template> </floating-root> </div> </div> <code>offset(0) / offset(10)</code> </article>
<article id="middleware-shift" class="middleware-card"> <MiddlewareTitle id={shiftExample.id} /> <p>{shiftExample.description}</p> <div class="mw-stage mw-stage-scroll-x mw-stage-shift" tabindex="0" aria-label="Horizontally scrollable shift demo" > <span class="mw-scroll-hint">scroll horizontally</span> <div class="mw-track mw-track-wide"> <floating-root id="middleware-shift-root" open placement="top"> <button slot="reference" class="mw-reference mw-reference-shift"> Reference </button> <template slot="content"> <div class="mw-panel mw-panel-lavender">Still on top</div> </template> </floating-root> </div> </div> <code>{'shift({padding: 8})'}</code> </article>
<article id="middleware-flip" class="middleware-card"> <MiddlewareTitle id={flipExample.id} /> <p>{flipExample.description}</p> <div class="mw-stage mw-stage-scroll-y mw-stage-flip" tabindex="0" aria-label="Vertically scrollable flip demo" > <span class="mw-scroll-hint">scroll up</span> <div class="mw-track mw-track-tall"> <floating-root id="middleware-flip-root" open> <button slot="reference" class="mw-reference mw-reference-flip"> Reference </button> <template slot="content"> <div class="mw-panel mw-panel-coral">Final placement</div> </template> </floating-root> </div> </div> <code>bottom (default) + flip() → shift()</code> </article>
<article id="middleware-arrow" class="middleware-card"> <MiddlewareTitle id={arrowExample.id} /> <p>{arrowExample.description}</p> <div class="mw-stage mw-stage-scroll-x mw-stage-arrow" tabindex="0" aria-label="Horizontally scrollable arrow demo" > <span class="mw-scroll-hint">scroll horizontally</span> <div class="mw-track mw-track-wide"> <floating-root id="middleware-arrow-root" open placement="top"> <button slot="reference" class="mw-reference mw-reference-arrow"> Reference </button> <template slot="content"> <div class="mw-panel mw-panel-ink mw-panel-arrow"> Floating <floating-arrow class="mw-arrow" width={MIDDLEWARE_ARROW.width} height={MIDDLEWARE_ARROW.height} static-offset={MIDDLEWARE_ARROW.staticOffset} ></floating-arrow> </div> </template> </floating-root> </div> </div> <code>{'offset(GAP) → shift() → arrow({element})'}</code> </article>
<article id="middleware-size" class="middleware-card"> <MiddlewareTitle id={sizeExample.id} /> <p>{sizeExample.description}</p> <div class="mw-stage mw-stage-scroll-y mw-stage-size" tabindex="0" aria-label="Vertically scrollable size demo" > <span class="mw-scroll-hint">scroll vertically</span> <div class="mw-track mw-track-size"> <floating-root id="middleware-size-root" open> <button slot="reference" class="mw-reference mw-reference-size"> Reference </button> <div slot="floating" top-layer="none" class="mw-panel mw-panel-paper mw-panel-size" > <strong>Floating content</strong> <span>One</span><span>Two</span><span>Three</span> <span>Four</span><span>Five</span> </div> </floating-root> </div> </div> <code>{'size({apply: set maxWidth / maxHeight})'}</code> </article>
<article class="middleware-card" id="middleware-auto-placement" > <MiddlewareTitle id={autoExample.id} /> <p>{autoExample.description}</p> <div class="mw-stage mw-stage-scroll-y mw-stage-auto-placement" tabindex="0" aria-label="Vertically scrollable auto placement demo" > <span class="mw-scroll-hint">scroll vertically</span> <div class="mw-track mw-track-tall"> <floating-root id="middleware-auto-placement-root" open> <button slot="reference" class="mw-reference mw-reference-auto"> Reference </button> <template slot="content"> <div class="mw-panel mw-panel-mint mw-panel-auto">Final placement</div> </template> </floating-root> </div> </div> <code>{'autoPlacement({padding: 8}) → shift({padding: 8})'}</code> </article>
<article id="hide" class="middleware-card" > <MiddlewareTitle id={hideExample.id} /> <p>{hideExample.description}</p> <div class="mw-stage mw-stage-scroll-y mw-stage-hide" tabindex="0" aria-label="Vertically scrollable hide demo" > <span class="mw-scroll-hint">scroll up</span> <div class="mw-track mw-track-tall"> <floating-root id="middleware-hide-root" open> <button slot="reference" class="mw-reference mw-reference-hide"> Reference </button> <div slot="floating" top-layer="none" class="mw-panel mw-panel-paper mw-panel-hide" > Floating </div> </floating-root> </div> </div> <div class="mw-state-readout" aria-live="polite">State: attached</div> <code>{"hide() + hide({strategy: 'escaped'})"}</code> </article>
<article id="middleware-inline" class="middleware-card"> <MiddlewareTitle id={inlineExample.id} /> <p>{inlineExample.description}</p> <div class="mw-inline-comparison" aria-label="Inline middleware comparison"> <div class="mw-inline-case"> <span class="mw-demo-label">without</span> <floating-root id="middleware-inline-without-root" open placement="top"> <p> Text before <floating-reference> <span class="mw-inline-reference"> a reference that wraps over multiple lines </span> </floating-reference> after. </p> <template slot="content"> <div class="mw-panel mw-panel-paper mw-panel-inline"> Bounding box </div> </template> </floating-root> </div> <div class="mw-inline-case"> <span class="mw-demo-label">with inline()</span> <floating-root id="middleware-inline-with-root" open placement="top"> <p> Text before <floating-reference> <span class="mw-inline-reference"> a reference that wraps over multiple lines </span> </floating-reference> after. </p> <template slot="content"> <div class="mw-panel mw-panel-ink mw-panel-inline"> Client rect </div> </template> </floating-root> </div> </div> <code>inline() → offset(8)</code> </article> </section></section>
<script> import { FloatingRootElement, arrow, autoPlacement, flip, hide, inline, offset, shift, size, } from '@floating-ui-plus/web-components'; import {MIDDLEWARE_ARROW} from '../../middleware-registry'; import {initializeExample} from './initialize-example';
initializeExample('middleware', (scope) => { const offsetZero = FloatingRootElement.query( scope, '#middleware-offset-0-root', ); const offsetTen = FloatingRootElement.query( scope, '#middleware-offset-10-root', ); offsetZero.middleware = [offset(0)]; offsetTen.middleware = [offset(10)];
const shiftStage = scope.querySelector<HTMLElement>('.mw-stage-shift'); const shiftRoot = FloatingRootElement.query(scope, '#middleware-shift-root'); shiftRoot.middleware = [ shift({ boundary: shiftStage ?? undefined, padding: 8, rootBoundary: 'document', }), ];
const flipStage = scope.querySelector<HTMLElement>('.mw-stage-flip'); const flipRoot = FloatingRootElement.query(scope, '#middleware-flip-root'); flipRoot.middleware = [ offset(8), flip({ boundary: flipStage ?? undefined, padding: 8, rootBoundary: 'document', }), shift({ boundary: flipStage ?? undefined, padding: 8, rootBoundary: 'document', }), ];
const arrowStage = scope.querySelector<HTMLElement>('.mw-stage-arrow'); const arrowRoot = FloatingRootElement.query(scope, '#middleware-arrow-root'); const arrowElement = scope.querySelector<HTMLElement>('floating-arrow'); arrowRoot.middleware = [ offset(MIDDLEWARE_ARROW.gap), shift({ boundary: arrowStage ?? undefined, padding: 8, rootBoundary: 'document', }), ...(arrowElement ? [arrow({element: arrowElement, padding: 8})] : []), ];
const sizeStage = scope.querySelector<HTMLElement>('.mw-stage-size'); const sizeRoot = FloatingRootElement.query(scope, '#middleware-size-root'); sizeRoot.middleware = [ offset(8), size({ boundary: sizeStage ?? undefined, padding: 8, rootBoundary: 'document', apply({availableWidth, availableHeight, elements}) { Object.assign(elements.floating.style, { maxWidth: `${Math.max(0, availableWidth)}px`, maxHeight: `${Math.max(0, availableHeight)}px`, }); }, }), ];
const autoStage = scope.querySelector<HTMLElement>( '.mw-stage-auto-placement', ); const autoRoot = FloatingRootElement.query( scope, '#middleware-auto-placement-root', ); autoRoot.middleware = [ autoPlacement({ boundary: autoStage ?? undefined, padding: 8, rootBoundary: 'document', }), shift({ boundary: autoStage ?? undefined, padding: 8, rootBoundary: 'document', }), ];
const hideStage = scope.querySelector<HTMLElement>('.mw-stage-hide'); const hideRoot = FloatingRootElement.query(scope, '#middleware-hide-root'); hideRoot.middleware = [ offset(8), hide({ boundary: hideStage ?? undefined, rootBoundary: 'document', }), hide({ boundary: hideStage ?? undefined, strategy: 'escaped', rootBoundary: 'document', }), ];
const withoutInline = FloatingRootElement.query( scope, '#middleware-inline-without-root', ); const withInline = FloatingRootElement.query( scope, '#middleware-inline-with-root', ); withoutInline.middleware = [offset(8)]; withInline.middleware = [inline(), offset(8)];
const syncObservableState = async () => { await Promise.all([ flipRoot.updatePosition(), autoRoot.updatePosition(), hideRoot.updatePosition(), ]); const flipPanel = flipRoot.floatingElement; const autoPanel = autoRoot.floatingElement; if (flipPanel) { flipPanel.dataset.placement = flipRoot.controller.position.placement; flipPanel.textContent = `Final: ${flipRoot.controller.position.placement}`; } if (autoPanel) { autoPanel.dataset.placement = autoRoot.controller.position.placement; autoPanel.textContent = `Final: ${autoRoot.controller.position.placement}`; } const data = hideRoot.controller.position.middlewareData.hide as | {referenceHidden?: boolean; escaped?: boolean} | undefined; const referenceHidden = Boolean(data?.referenceHidden); const escaped = Boolean(data?.escaped); const panel = hideRoot.floatingElement; panel?.setAttribute( 'data-reference-hidden', String(referenceHidden), ); panel?.setAttribute('data-escaped', String(escaped)); const readout = scope.querySelector<HTMLElement>( '#hide .mw-state-readout', ); if (readout) { readout.textContent = `State: ${ referenceHidden ? 'reference hidden' : escaped ? 'floating escaped' : 'attached' }`; } };
[flipStage, autoStage, hideStage].forEach((stage) => { stage?.addEventListener('scroll', () => void syncObservableState(), ); }); requestAnimationFrame(() => { if (shiftStage) shiftStage.scrollLeft = 360; if (flipStage) flipStage.scrollTop = 160; if (arrowStage) arrowStage.scrollLeft = 520; if (sizeStage) sizeStage.scrollTop = 280; if (autoStage) autoStage.scrollTop = 130; if (hideStage) hideStage.scrollTop = 160; void Promise.all([ shiftRoot.updatePosition(), arrowRoot.updatePosition(), sizeRoot.updatePosition(), withoutInline.updatePosition(), withInline.updatePosition(), syncObservableState(), ]); });
if (window.location.hash === '#hide') { requestAnimationFrame(() => { scope.querySelector('#hide')?.scrollIntoView({block: 'center'}); }); } });</script><script setup lang="ts">import MiddlewareCase from '../components/MiddlewareCase.vue';import {MIDDLEWARE_EXAMPLES} from '../../middleware-registry';</script>
<template> <div class="vue-middleware-grid"> <MiddlewareCase v-for="item in MIDDLEWARE_EXAMPLES" :key="item.id" :index="item.number" :kind="item.id" :title="item.title" :doc-href="`https://floating-ui.com/docs/${item.docsPath}`" :detail="item.description" /> </div></template>The gallery isolates one positioning concern per card:
| Card | Behavior | Composition |
|---|---|---|
| Offset | Change the main-axis gutter | offset(0) vs offset(10) |
| Shift | Keep the panel inside the boundary | shift({padding: 8}) |
| Flip | Choose another side when space runs out | flip() → shift() |
| Arrow | Add an arrow without changing the requested gap | offset() → arrow() |
| Size | Constrain available dimensions | size({apply}) |
| Auto placement | Choose the roomiest side | autoPlacement() → shift() |
| Hide | Report clipping and escaped references | hide() strategies |
| Inline | Use the relevant rect of wrapped inline content | inline() → offset(8) |
For example, the arrow card uses the requested visual gap and registers the arrow element separately:
root.middleware = [ offset(3), shift({padding: 8}), arrow({element: arrowElement}),];Scroll each card in the live demo to see the middleware react to clipping, available space, and wrapped inline references. See the Web framework guide for the framework-neutral middleware contract.