In CSS, if no element on the page declares a z-index, in what order does the browser paint overlapping boxes?
answer
- there is always a defined order
- backgrounds, blocks, floats, text, positioned
- positioned beats static regardless of markup
- document order breaks ties, later wins
- negative sinks below content, not below background
basics
~20 sWithin a stacking context CSS paints in a fixed sequence: the context element's background and borders, then in-flow block-level descendants, then floats, then inline content, then positioned descendants in document order. So any positioned element covers unpositioned content regardless of markup order.
solid answer
~40 sThere is a defined order even with no `z-index` anywhere. Inside a stacking context, the browser paints the context element's own background and borders first, then any descendants with a negative stack level, then in-flow block-level boxes, then floats, then in-flow inline content, then positioned descendants with `z-index: auto` or `0`, and finally positive stack levels. Two consequences are worth stating out loud. First, a positioned element paints above *all* unpositioned content no matter where it sits in the markup — the earlier `position: relative` div covers the later static one. Second, floats paint below inline content, which is exactly why text flows visibly on top of a float's background rather than behind it. Among elements at the same step, document order decides, and the later element wins.
code
html · 6 lines<style>
.first { position: relative; background: teal; height: 40px; }
.second { background: gold; height: 40px; margin-top: -20px; }
</style>
<div class="first">positioned, earlier in the DOM</div>
<div class="second">static, later in the DOM</div>go deeper
Know that a positioned element paints over unpositioned ones even when it comes first in the markup, and that among equals the later element in the source wins.
Walk the sequence: context background, negative levels, in-flow blocks, floats, inline content, positioned elements, positive levels — and explain why text paints over a float's background.
Use the order diagnostically: reason about why a decorative negative-level layer disappeared, or why removing position: relative from a wrapper suddenly reshuffled a component's internals.
Own the implication for shared components: relying on implicit paint order makes a component's appearance depend on its container's markup, so decide when to make layering explicit instead.
## There is always an order "No z-index" does not mean "undefined". CSS specifies a complete painting order for every stacking context, and `z-index` is just a way to opt into two extra bands around it. Knowing the default order explains a surprising number of overlaps that look arbitrary. ## The sequence, back to front Within one stacking context, the browser paints: 1. The **background and borders of the element that created the context**. 2. Child stacking contexts with **negative** stack levels (most negative first). 3. **In-flow, non-inline-level, non-positioned** descendants — ordinary block boxes and their backgrounds. 4. **Non-positioned floats**. 5. **In-flow, inline-level, non-positioned** descendants — text and inline boxes. 6. **Positioned descendants with `z-index: auto` or `0`**, plus child stacking contexts at level 0. 7. Child stacking contexts with **positive** stack levels (least positive first). Within any single step, elements are painted in document order, so a later sibling paints over an earlier one. ## Consequence one: positioning alone lifts an element Step 6 comes after steps 3–5, so **any** positioned element paints above **every** unpositioned one in the same context — even if the positioned element appears first in the markup: ```html <div style="position: relative; background: teal">first, positioned</div> <div style="background: gold; margin-top: -20px">second, static</div> ``` The teal box wins. That is why `position: relative` with no offsets and no `z-index` is a real, working "bring to front" — a trick people use without knowing why it works. ## Consequence two: floats sit between blocks and text Floats are painted at step 4: above block backgrounds, below inline content. This is not an accident; it is what makes text wrapping look right. The float's own background sits behind the text that flows around and over it. If floats painted after inline content, a float overlapping a line box would hide the words. ## Consequence three: negative levels sink but do not vanish Step 2 is above step 1. A child with `z-index: -1` therefore paints **behind its stacking-context parent's in-flow content but in front of that parent's own background**. This is what makes the decorative-pseudo-element pattern work: `::before` with `z-index: -1` sits behind the text and in front of the card's background. The pattern breaks when the parent is not a stacking context. Then the negative child belongs to some further ancestor's context, sinks past the parent's background, and disappears. Adding `isolation: isolate` (or `position: relative; z-index: 0`) to the parent contains it and restores the effect. ## Consequence four: document order is the tie-break, not a fallback People sometimes describe document order as "what happens when CSS gives up". It is the specified tie-break within a step. Two positioned siblings with the same stack level, or none, are ordered by source position — later on top. That is the entire explanation for "why does my second dropdown cover my first". ## Where the order restarts Every nested stacking context runs this whole sequence again, internally, and the result is painted atomically at the position its root occupies in the parent's sequence. So the order is recursive: a tree of contexts, each internally sorted, each an indivisible blob to its parent. ## Saying it well in an interview You are not expected to recite seven numbered steps. What earns the point is the shape: *backgrounds, then in-flow blocks, then floats, then inline text, then positioned things, in document order within each band, and z-index only adds bands at the ends.* Then land the two consequences — positioned beats unpositioned regardless of markup order, and text paints over a float's background — because those are the behaviours the question is really testing.
- Why does position: relative with no offsets and no z-index still bring an element forward?Because positioned descendants are painted in a later step than in-flow, non-positioned content. Adding `position: relative` moves the element from the block/inline bands into the positioned band without shifting it a pixel, so it now paints over static siblings regardless of document order.
- A decorative ::before with z-index: -1 vanished instead of sitting behind its parent's text. Why?The parent did not create a stacking context, so the pseudo-element joined an outer context and sank behind that ancestor's opaque background. Negative levels paint above their own context root's background but below its content — give the parent `isolation: isolate` or `position: relative; z-index: 0` so it becomes that root.
- Where do floats sit in the painting order, and why does it matter?Floats paint after in-flow block boxes but before in-flow inline content. That is what makes text wrapping legible: the wrapped text paints on top of the float's background rather than behind it. It also means a float's background can cover an earlier block's background while still sitting under nearby text.
saying these in an interview costs you the question
- Says painting order is undefined without z-index
- Claims markup order alone decides which box wins
- Thinks floats paint above the text that wraps them
- Believes z-index: -1 always hides an element completely
- Assumes the order is not recursive across nested contexts