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.
🌐 client — runs in the browser (element JS API) 🖥️ server — LiveView round-trip
Ancestor CSS under test: (none) — dropdown anchors to the viewport
The simplest case: no ancestor establishes a containing block for fixed positioning. The dropdown anchors to the viewport and sits under the input.
(none)
Ancestor CSS under test: transform: translateZ(0)
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)
Ancestor CSS under test: container-type: inline-size
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)
Ancestor CSS under test: contain: paint — toggle it to trigger a console.warn
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.
On phones the dropdown switches to a position: fixed, full-viewport
sheet
(see Responsive & Mobile) instead of
anchoring under the input. The same containing-block rule applies — but the failure mode is worse.
A fixed element whose ancestor establishes a containing block is anchored to that ancestor's box,
not the viewport, so the sheet no longer covers the screen: it ends up offset, clipped, or mis-sized.
Note the asymmetry with PO02: an ancestor transform
is harmless
for the floating
dropdown (it just anchors under the input), but it breaks
the fullscreen sheet, because the sheet
needs the viewport as its containing block to span it. The properties that trigger this are the ones
browsers reliably honor — transform, perspective, filter,
backdrop-filter, and qualifying will-change. contain
and
container-type
do not
break the sheet (browsers don't honor them for fixed
positioning), which is exactly why the floating path has to override them but the sheet doesn't care.
Because nothing anchors the sheet, there's no drift to measure the way PO04 does. Instead the library
asks its containing-block heuristic, when the sheet opens, whether the true offset parent is the viewport
or an element — and if it's an element, fires a console.warn
once per instance pointing at
the culprit. The consumer-side fixes are the same: move the <web-multiselect>
out of
that subtree, or remove the property.
[@keenmate/web-multiselect] Fullscreen overlay is anchored to an ancestor <div.wrap-transform> (has transform) instead of the viewport, so it may not cover the screen (offset, clipped, or mis-sized). An ancestor of <web-multiselect> establishes a fixed-positioning containing block (transform / perspective / filter / backdrop-filter / will-change). Fix on your side: move the component out of that ancestor's subtree, OR remove/replace that property. 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.
(pick an option anywhere on this page)