What makes a browser give an element its own compositing layer, and why can promoting one element cause neighbouring elements to get layers too?
answer
- not a property you declare
- 3D transform, animation, video, hint
- the compositor draws whole layers
- paint order must survive
- one promotion can cascade
basics
~20 sBrowsers promote content that must be drawn independently: 3D transforms, running transform or opacity animations, a will-change hint, video and WebGL canvas, and some fixed or scrolling content. Content that paints above a promoted layer may also need its own layer so the original paint order survives.
solid answer
~40 sPromotion is a heuristic, not something you declare directly. The usual triggers are a 3D transform such as `translateZ(0)` or `rotate3d()`, a compositor-driven animation of `transform` or `opacity` currently running, a `will-change` declaration naming a compositable property, `<video>` and canvases with a WebGL context, and in some engines `position: fixed` and `backdrop-filter`. The second-order effect is the one people miss: the compositor draws whole layers in order, so if an ordinary element paints on top of a promoted one, the browser cannot keep the correct paint order by drawing that element into the base layer — it may have to promote the overlapping content as well. One `translateZ(0)` under a busy region can therefore multiply into many layers. Modern Chromium reduced how often that happens, but the effect is still real.
go deeper
Know that only some content gets its own compositing layer, and be able to name a couple of triggers such as a 3D transform, a running transform animation, or a <video> element.
Be ready to explain that layerization is an engine heuristic, list the common triggers, and describe why an element painting above a promoted layer may need a layer of its own to preserve paint order.
Show that you verify rather than assume: open the layer tooling, count layers, and connect a high count to GPU memory, texture eviction on low-end devices, and per-layer draw overhead.
Own the fact that layerization is unspecified and engine-specific, so a design cannot depend on it. Decide what the product does when the heuristics change under you, and keep an explicit texture-memory budget for the lowest-end device you support.
## Layers are the browser's decision, not yours There is no CSS property that says "give this element a compositing layer". You express intent (`will-change`) or you use a feature whose implementation needs one, and the engine's layerization pass decides. That is why the honest answer to "how do I force a layer" is "you nudge, and then you verify in devtools". ## The common promotion triggers - **3D transforms.** `transform: translateZ(0)`, `translate3d(0,0,0)` or `rotate3d(...)` have historically been the reliable promotion hack, because a 3D transform has to be applied by the compositor. - **A running compositor animation.** When a CSS transition, CSS animation or Web Animations animation of `transform` or `opacity` starts, the engine promotes the element so the animation can be ticked without the main thread, and typically un-promotes it when the animation finishes. - **`will-change`.** Naming `transform`, `opacity` or `filter` tells the engine to prepare, which in practice usually means creating the layer up front. - **Media and GPU surfaces.** `<video>` elements and `<canvas>` with a WebGL/WebGPU context own a surface that the compositor draws directly. - **`backdrop-filter`.** Reading and blurring what is behind an element requires the backdrop to exist as a separate composited input. - **Engine-specific cases.** Chromium commonly composites `position: fixed` elements and composited scrolling containers so that scrolling stays off the main thread; cross-origin iframes are composited surfaces of their own. Say in an interview that this list is heuristic and engine-specific rather than reciting it as a specification. It is not one. ## The overlap effect: one layer becomes many The compositor draws layers as whole units, in order. Suppose a card is promoted, and an absolutely positioned badge paints on top of it in the document's paint order. If the badge stayed in the base document layer, the compositor would have to draw the base layer, then the card on top — and the badge, being part of the base layer, would end up *under* the card. Wrong result. The engine's way out is to give the badge its own layer too and draw it after the card. This is **overlap-induced promotion**, and it is why a single `translateZ(0)` on a header can end up producing a dozen layers in a page that overlaps it heavily. ```css /* one promotion... */ .sticky-header { transform: translateZ(0); } /* ...and everything that paints over it may need a layer to stay on top */ .dropdown, .tooltip, .badge { /* candidates for overlap-induced promotion */ } ``` Chromium's rearchitecture of this stage (CompositeAfterPaint, shipped around Chrome 88 in early 2021) made layerization a decision taken after painting, which cut down on speculative overlap promotions considerably — but overlapping content that must draw above a composited layer can still require one, and the general principle holds in every engine. ## Why the count matters Each layer is at minimum a texture: roughly width times height times four bytes. A single full-screen layer at 1920×1080 is about 8 MB of GPU memory. Layers also carry per-layer bookkeeping in the compositor tree and per-layer draw calls per frame. A page with hundreds of layers can spend more time managing them than it saves by avoiding repaints, and on a memory-constrained phone it can trigger texture eviction, which shows up as flashes of unpainted content. There is a rendering side effect too: content in a composited layer is often rasterized without subpixel (LCD) text antialiasing, because the layer may be drawn over unknown content. Promoted text can therefore look subtly different from the same text in the base layer. ## How to look Every engine's devtools expose the layer tree — Chromium has a Layers panel and a "Layer borders" rendering overlay; Safari's Web Inspector has a Layers tab; Firefox exposes compositing information in its profiler. The correct habit is to promote deliberately, then open the tool and confirm the layer count is what you expected, rather than assuming the hint did what you meant.
- Why has `translateZ(0)` historically been used as a promotion hack rather than something more explicit?Because a 3D transform must be applied by the compositor, so declaring one reliably produced a layer long before there was a standard way to ask. `will-change` was introduced to express the same intent declaratively, without lying about the element's geometry.
- Does an element stay promoted after its transform animation finishes?Usually not. Engines promote for the duration of a compositor-driven animation and drop the layer afterwards, so the memory is reclaimed. That is also why a persistent `will-change` declaration behaves differently from an animation: it keeps the layer alive indefinitely.
- How would you confirm how many layers a page actually has?Use the engine's layer tooling rather than reasoning from CSS: Chromium's Layers panel and its Layer borders overlay, Safari's Layers tab in Web Inspector, Firefox's compositing data in the profiler. They show each layer, its size, and often the reason it was created.
saying these in an interview costs you the question
- Thinks a CSS property directly creates a compositing layer
- Assumes promotion is specified identically across browsers
- Believes layers are free because the GPU handles them
- Says only the element you promoted gets a layer
- Treats translateZ(0) as a general performance fix