Why CSS Filter Breaks Position Fixed
Applying a CSS filter to a parent element breaks child
position: fixed viewport anchoring because the CSS
specification mandates that any non-none filter establishes a new
containing block for all descendants. Consequently, instead of anchoring
relative to the browser viewport, the fixed child positions itself
relative to the filtered parent, effectively behaving like an absolutely
positioned element.
The CSS Specification Behind the Behavior
Under normal circumstances, an element styled with
position: fixed calculates its coordinates
(top, left, right,
bottom) relative to the initial containing block, which
corresponds to the browser viewport. This allows fixed elements—such as
navigation bars, modals, and floating action buttons—to remain
stationary when the user scrolls the page.
However, according to the W3C CSS Filter Effects Module Level 1
specification, any value other than none for the
filter property causes the element to become the containing
block for all descendants, including fixed-position elements.
To apply a visual filter (such as blur,
grayscale, or drop-shadow), the browser's
rendering engine must isolate the target element and its entire subtree
into an offscreen bitmap buffer. The graphical operations are then
computed on that flattened buffer before drawing it back onto the
screen. Because the filtered element must be processed as a
self-contained coordinate space, the viewport's reference frame cannot
pierce through this boundary. As a result, the filtered parent
intercepts the child’s fixed coordinates.
Related Properties with the Same Effect
The filter property is not the only CSS declaration that
alters the containing block for fixed elements. The same behavior is
triggered by any property that isolates rendering or modifies the
coordinate matrix, including:
transform(any value other thannone)perspective(any value other thannone)backdrop-filter(any value other thannone)contain(with values such aspaint,layout,strict, orcontent)will-change(if set to any property that generates a containing block, such aswill-change: filter)
How to Fix the Problem
Because this behavior is intentional according to the CSS specification, it cannot be overridden using custom CSS on the child element. To maintain viewport anchoring, you must change how the DOM or styling is structured:
- Move the Fixed Element Outside the Filtered
Container: Relocate the fixed element in the HTML hierarchy so
that it is a sibling of the filtered element or placed directly under
the
<body>tag. In JavaScript frameworks, this can be achieved using portals (such as React'screatePortalor Vue's<Teleport>). - Apply the Filter to a Dedicated Layer: Instead of
applying the filter to a general wrapper that holds both fixed elements
and regular content, apply the filter exclusively to the specific
elements that require it, or use an absolutely positioned pseudo-element
(
::beforeor::after) behind your content.