For an element with position: absolute, how does the browser decide which box is its containing block, and what do top: 0 and width: 50% then resolve against?
answer
- "positioned relative to what?"
- walk up until an ancestor qualifies
- non-static, or a transform-like property
- padding box, not content box
- none found means document-anchored, not viewport-fixed
basics
~20 sThe browser walks up the ancestor chain for the nearest element whose position is not static and uses that ancestor's padding box as the containing block. Insets and percentage sizes resolve against that box; with no such ancestor, the initial containing block is used.
solid answer
~40 sThe containing block for an absolutely positioned box is the padding box of its nearest ancestor whose `position` is `relative`, `absolute`, `fixed` or `sticky`. Certain properties also qualify a static ancestor — `transform`, `filter`, `perspective`, `contain: layout` or `paint`, and `container-type` — because they create a containing block for positioned descendants. If nothing in the chain qualifies, the containing block is the initial containing block, a viewport-sized rectangle anchored at the document origin, which is why an orphaned absolute element lands at the top of the *page* and scrolls with it. Everything geometric then resolves against that box: `top: 0` sits on its padding edge, `width: 50%` is half its padding-box width, and percentage padding and margins on the positioned element resolve against its width too.
code
css · 12 lines.panel {
position: relative;
width: 300px;
padding: 20px;
}
.panel .flag {
position: absolute;
top: 0;
left: 0;
width: 50%;
}go deeper
Know the core sentence: an absolute box measures from the nearest ancestor that is positioned, and from the page itself if there is none. Being able to add position: relative to the right wrapper is the working skill here.
Explain the lookup precisely — the ancestor chain, the non-static test, and the padding box as the rectangle — and say what percentages of width and height resolve against once it is found.
Demonstrate the debugging walk: inspect ancestors for position and for the containing-block-creating properties like transform and contain, and recognise a page-corner landing as the initial-containing-block fallback.
Speak to the architectural angle: components should declare their own anchor rather than depending on an ancestor a consumer might not provide, so overlay-bearing components stay portable across pages that style their wrappers differently.
## The question behind the question "Positioned relative to what?" is the single most common source of surprise in CSS positioning, and the spec answers it with one concept: the **containing block**. Every box has one, and it supplies the rectangle that percentages and offsets are measured against. For an in-flow block box the containing block is simply the parent's content box. Absolute positioning is what makes the rule interesting, because it does not use the parent. ## The lookup rule For `position: absolute`, the browser walks up the ancestor chain and stops at the first ancestor that is a containing block for absolutely positioned descendants. An ancestor qualifies when either: - its `position` is anything other than `static` — `relative`, `absolute`, `fixed` or `sticky`; or - it is still static but carries a property that creates a containing block anyway: a `transform` (or the individual `translate`, `rotate`, `scale` properties), a non-`none` `filter` or `backdrop-filter`, a `perspective`, `contain: layout`, `paint`, `content` or `strict`, `will-change` naming one of those properties, or a `container-type` other than `normal`. The rectangle taken from that ancestor is its **padding box** — the border box minus borders, i.e. content plus padding. This trips people up: a wrapper with `padding: 20px` gives an `inset: 0` child a box 40px wider than the wrapper's content area, so an "overlay" laid over it covers the padding too. That is usually what you want, but it means the numbers do not match the content-box width you had in mind. If the walk reaches the root without finding a qualifying ancestor, the containing block is the **initial containing block**: a rectangle the size of the viewport, anchored at the origin of the document canvas. It is viewport-*sized* but document-*anchored*, so `top: 0` puts the element at the top of the document and it scrolls out of view like anything else. Confusing that with `position: fixed` is a classic mistake. ## What resolves against it Once the containing block is known, everything geometric on the absolutely positioned box uses it: - `top`, `right`, `bottom`, `left` and the `inset` shorthand are distances from the corresponding padding edges. - Percentage `width`, `max-width` and horizontal `padding`/`margin` resolve against the containing block's **width**; percentage `height` resolves against its **height** — which is a definite height here even if the ancestor sized itself from its content, because the ancestor's box is laid out before the out-of-flow box is positioned. - Percentage vertical padding and margins still resolve against the containing block's *width*, exactly as they do in normal flow. ```css .panel { position: relative; width: 300px; padding: 20px; /* padding box is 340px wide */ } .panel .flag { position: absolute; top: 0; left: 0; /* the padding edge, not the content edge */ width: 50%; /* 170px — half of 340px */ } ``` ## Why fixed is different — and why it sometimes isn't `position: fixed` skips the search: its containing block is the viewport, so it does not scroll. The exception is that the same containing-block-creating properties listed above — `transform`, `filter`, `contain`, `container-type` and friends — capture fixed descendants too, which is the one case where fixed elements start scrolling with the page. ## Debugging it When an absolutely positioned element lands somewhere unexpected, the diagnosis is mechanical rather than creative. Walk up from the element and ask, at each ancestor, "is this a containing block?" — check `position` first, then the exotic list. The first "yes" is your anchor. If the element flew to the top of the page, the answer was "no" all the way up and you are looking at the initial containing block. If it anchored to an ancestor you did not intend, something in between qualified — often a `transform` on an animation wrapper, or a `contain` on a component root, neither of which announces itself as positioning code. Devtools help here: computed styles show `position` per element, and inspecting the offending ancestor for `transform` or `contain` is a five-second check once you know the list exists. ## The design consequence Because the anchor is chosen by proximity, you get to place it deliberately. Put `position: relative` on the exact box you want a child to measure against — the component root for a dropdown, the figure for a caption — and the child's insets become meaningful and stable. Leaving it off and hoping is how elements end up in the page corner.
- Why does an absolutely positioned overlay with inset: 0 cover a parent's padding as well as its content?Because the containing block is the ancestor's padding box, not its content box. `inset: 0` places each edge on the padding edge, so padding is inside the covered area. If you need the overlay to stop at the content edge, you have to offset it by the padding amount or move the padding onto an inner element.
- Does a percentage height on an absolutely positioned element behave differently from one on an in-flow block?Yes. An in-flow block's percentage height needs its parent to have a definite height, or it computes to `auto`. An absolutely positioned box always resolves the percentage against its containing block's height, because that ancestor's box has already been laid out by the time the out-of-flow box is positioned.
- An element jumps to the top-left of the page instead of its wrapper — what is your first check?Whether any ancestor is actually positioned. Landing at the page corner means the search found no qualifying ancestor and fell back to the initial containing block, so the fix is usually a missing `position: relative` on the intended anchor — not adjusting the offsets.
saying these in an interview costs you the question
- Says the containing block is always the parent element
- Uses the ancestor's content box instead of its padding box
- Claims no positioned ancestor means positioning against the viewport
- Forgets that transform or contain on a static ancestor qualifies it
- Thinks percentage height on an absolute box needs a fixed-height parent