How the dropdown anchors across CSS containing-block scenarios, and what the library does when it can't position the panel correctly on its own.
The simplest case: no ancestor establishes a containing block for fixed positioning. The dropdown anchors to the viewport and sits under the input.
(none)
transform, perspective, filter, backdrop-filter,
and qualifying will-change
values establish a containing block for fixed-positioned
descendants in every browser. Floating UI detects them, the browser honors them, and the dropdown
anchors to the wrapper — under the input, as expected.
transform: translateZ(0)
container-type
establishes a containing block per spec, and Floating UI's default
getOffsetParent
walks up to use the wrapper. But for some real-world layouts
(notably pure-admin's .pa-layout__main
wrapping a shadow-DOM component), the browser
does not
actually anchor the fixed panel there — and Floating UI's coordinates
end up offset by the wrapper's viewport-x, leaving the dropdown stranded to the side of the input.
Since v1.10.1 the library installs a custom getOffsetParent
that ignores
container-type
and contain
in favor of properties the browser reliably
honors. The dropdown lands under the input even in this configuration.
container-type: inline-size (same as .pa-layout__main)
The library's containing-block heuristic isn't bulletproof — an ancestor can genuinely anchor
fixed positioning (per spec) without being on the library's reliable-properties list. Every
dropdown position pass verifies the panel landed where the library told the browser to put it.
If it drifts, a console.warn
fires once per multiselect instance with the likely
culprit element and its responsible CSS property.
Open DevTools → Console before
toggling the wrapper, then open the dropdown. With
contain: paint applied, the dropdown drifts and the warning fires.
(plain)
[@keenmate/web-multiselect] Dropdown panel rendered Npx / 0px away from where the library positioned it. Most likely culprit: <div.form-group#drift-wrapper> (has contain: paint). An ancestor of <web-multiselect> establishes a fixed-positioning containing block that the library's heuristic doesn't recognize. Fix on your side: replace the property with `transform: translateZ(0)` on that ancestor, OR move the trigger out of that ancestor's subtree. If neither is acceptable, please file an issue at https://github.com/keenmate/web-multiselect/issues with the ancestor's computed CSS.
The CSS spec lists seven properties as containing-block-establishing for fixed positioning:
transform, perspective, filter,
backdrop-filter, qualifying will-change,
contain (layout|paint|strict|content), and container-type.
In isolation, browsers honor all of them.
But in some shadow-DOM scenarios — a web-multiselect
dropdown lives inside the
shadow root of a custom element, while the containment-establishing ancestor is in light DOM —
the browser's actual position: fixed
layout disagrees with Floating UI's prediction
for contain and container-type.
The library resolves that disagreement
in favor of what every browser reliably
does, and the drift detector catches whatever
falls through.
If you hit the warning in your own app, the two consumer-side fixes are:
contain: … or container-type: …
on the ancestor with
transform: translateZ(0)
— same containing-block effect, but the browser
definitively anchors fixed positioning to it.
<web-multiselect>
to a different subtree that doesn't have the
problematic ancestor.