| Stacking contexts |
|---|
| Document root | The html element carries the root stacking context; every element on the page participates in it. | :root | The root background is the floor of the page — a z-index: -1 child paints behind text but never behind the html background. |
| Positioned + integer z-index | A positioned element with an integer z-index forms a new stacking context that traps its descendants. | position: relative; z-index: 2; | The position value alone does not create the context — the integer z-index does. |
| Positioned + z-index: auto | A positioned element with z-index: auto stays in its parent's stacking context and paints in the auto layer. | position: absolute; z-index: auto; | auto is not 0: auto creates no context, 0 creates one, yet both paint at the same level. |
| opacity < 1 | Any opacity below 1 forces the element into its own stacking context so the group composites as one layer. | opacity: 0.99; | A fade that ends at 0.99 instead of 1 permanently changes stacking — descendant z-index cannot escape it. |
| transform | Any transform value other than none creates a stacking context and a compositing layer. | transform: translateZ(0); | The old translateZ(0) GPU hack silently traps descendant z-index forever after. |
| filter / backdrop-filter | A filter or backdrop-filter value other than none makes the element a stacking context. | filter: blur(0px); | Even a no-op blur(0px) counts; will-change: transform or filter triggers the same before anything animates. |
| isolation | Creates a stacking context and does nothing else — the side-effect-free way to fence off a component. | isolation: isolate; | Reach for this instead of z-index hacks when a third-party widget's values must stay inside your layout. |
| position: fixed / sticky | Fixed and sticky elements form stacking contexts even with z-index: auto. | position: fixed; | A context is not a crown: a fixed header still needs z-index to beat later positioned siblings — and an ancestor with transform makes it behave like absolute. |
| Other triggers | mix-blend-mode other than normal, clip-path, mask, perspective, and contain: paint / layout / content each create a context. | mix-blend-mode: difference; | They stack up: an element with both opacity: 0.9 and a transform is still exactly one new context. |
| Painting order |
|---|
| 1. Backgrounds & borders | The stacking context element's own background and borders paint first, behind everything inside it. | background: #0b1020; border: 1px solid #333; | This is the floor of the context — no descendant ever paints below it. |
| 2. Negative z-index | Children with negative z-index paint behind in-flow content, ordered from most negative up. | z-index: -1; | The standard way to tuck a decorative layer behind text without leaving the parent. |
| 3. In-flow blocks | Non-positioned block descendants paint backgrounds and borders in tree order. | display: block; | Their text does not paint here — inline content waits for a later step. |
| 4. Floats | Non-positioned floats paint after in-flow block backgrounds, before inline content. | float: left; | A float sits above block backgrounds but below inline text — the classic wrap-around look. |
| 5. Inline content | All inline-level content paints after floats, on top of everything so far. | display: inline; | That is why plain text renders over a floated sibling's background. |
| 6. z-index: 0 / auto | Positioned elements with z-index 0 or auto paint after all of the above, in tree order. | position: relative; z-index: auto; | Same paint level as 0, different containment — 0 fences descendants, auto does not. |
| 7. Positive z-index | The top layer inside the context: higher integers paint over lower ones, ties break by tree order. | z-index: 1; | These values only compete inside their own context — they never reach over a sibling context. |
| Flex & grid children |
|---|
| Flex items | Flex items honor z-index with no position value at all. | .list { display: flex; } .item { z-index: 3; } | Advice that z-index needs position: relative predates flexbox — it stopped being true there. |
| Grid items | Grid items paint by z-index while position: static, which is how overlapping grid areas choose order. | .board { display: grid; } .cell { z-index: 2; } | Overlapping areas with no z-index fall back to source order. |
| z-index: auto on items | A flex or grid item with z-index: auto creates no context; paint order follows order-modified source order. | order: -1; | Changing order changes which overlapping item paints on top — reorder and the overlap flips. |
| Integer z-index on items | A flex or grid item with an integer z-index becomes a stacking context even at position: static. | z-index: 2; | Same rule as positioned elements: values inside the item cannot escape the item. |
| Values that matter |
|---|
| auto | Paints at the base auto level and creates no stacking context of its own. | z-index: auto; | Reset with auto, not 0, when you want to dissolve containment as well as level. |
| 0 | Creates a stacking context at the base level — a containment fence that changes no paint order. | z-index: 0; | position: relative + z-index: 0 isolates a component without joining any arms race. |
| 1 | Beats every auto and 0 sibling inside the same context. | z-index: 1; | One level above baseline is usually all a local overlay or badge needs. |
| 9999 | A smell, not a scale: it wins today's fight and starts tomorrow's escalation. | z-index: 9999; | A 9999 modal inside a low-level context still loses to any sibling of a higher context — the ancestor's level caps it. |
| Integers only | z-index accepts integers and auto; nothing else parses. | z-index: 1.5; | A decimal is an invalid value, so the whole declaration is dropped and the earlier one wins. |
| Bounds | Values clamp to ±2147483647, the 32-bit signed integer ceiling. | z-index: 2147483647; | Browsers clamp out-of-range values, so 99999999 paints exactly like the max — the cap ends every escalation. |