CSS Grayscale Filter Calculation and Child Elements
The CSS filter: grayscale(100%) function transforms an
element's visual appearance by converting all of its colors into shades
of gray. This conversion relies on a standardized luminance algorithm
based on human visual perception rather than a simple average of RGB
values. Furthermore, applying this filter affects not just the target
element itself, but alters the rendering pipeline for all nested child
elements, creating specific structural side effects such as new stacking
contexts and irreversible inherited visual styles.
The Grayscale Calculation
Under the W3C Filter Effects Module Level 1 specification,
filter: grayscale() uses a linear color matrix
transformation to determine luminance. Human eyes do not perceive all
wavelengths of light equally; green appears substantially brighter than
red, which in turn appears brighter than blue.
To account for human perception, the browser applies the ITU-R BT.709 color space coefficients to the color channels:
- Red: 0.2126 (21.26%)
- Green: 0.7152 (71.52%)
- Blue: 0.0722 (7.22%)
When grayscale(100%) (or a factor of 1) is
applied, the browser calculates the gray intensity value (\(Y\)) for every pixel:
\[Y = (0.2126 \times R) + (0.7152 \times G) + (0.0722 \times B)\]
The resulting intensity value \(Y\) is then assigned equally to the red, green, and blue channels of the output pixel:
- \(R_{out} = Y\)
- \(G_{out} = Y\)
- \(B_{out} = Y\)
Alpha (opacity) values are left unaffected during this transformation.
For partial values (such as grayscale(50%)), the browser
uses an interpolation matrix where the identity matrix blends
proportionally into the luminance matrix:
\[\begin{bmatrix} 0.2126 + 0.7874(1 - d) & 0.7152 - 0.7152(1 - d) & 0.0722 - 0.0722(1 - d) \\ 0.2126 - 0.2126(1 - d) & 0.7152 + 0.2848(1 - d) & 0.0722 - 0.0722(1 - d) \\ 0.2126 - 0.2126(1 - d) & 0.7152 - 0.7152(1 - d) & 0.0722 + 0.9278(1 - d) \end{bmatrix}\]
When \(d = 1\) (100%), all rows collapse identically to \([0.2126, 0.7152, 0.0722]\).
How It Affects Child Elements
Applying filter: grayscale(100%) affects descendant
elements in two critical ways: visual flattening and structural layout
behavior.
1. Visual Composition and Non-Reversibility
The filter property is technically not inherited through
CSS cascade rules—child elements do not automatically receive
filter: grayscale(100%) on their own computed styles.
Instead, the filter is applied post-composition.
The browser first renders the parent element along with all of its child elements off-screen as a single graphical buffer (bitmap). Once this combined layer is rasterized, the grayscale algorithm runs on the entire output.
Because the filter applies to the flattened result of the parent container:
- Children cannot opt out: Setting
filter: noneorfilter: grayscale(0%)on a child element will not restore its color. The child's colors are already converted during the final rendering pass of the parent. - Child filters chain additively: Applying another
filter (like
filter: sepia(100%)) to a child element will apply before the parent's grayscale filter, meaning the child will be converted to sepia first and then stripped of all color by the parent's grayscale pass.
2. Stacking Context and Containing Block Behavior
Any non-default value on the filter property alters the
DOM element's behavior in the layout engine:
- Creates a New Stacking Context: The filtered
element behaves like an element with an explicit
z-index, isolating its children's layering relative to outside elements. - Becomes a Containing Block: The filtered element
becomes the containing block for all absolute
(
position: absolute) and fixed (position: fixed) descendant elements. As a result, fixed-position children will anchor to the filtered ancestor rather than the viewport.