Skip to content
llms.txt
llms.txt

Middleware

Open the live middleware demo

Live demo

Middleware

Source for this preview

MiddlewareExamples.astro
---
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>

Open the full demo ↗

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.