skip to content

Positioning and Stacking

How elements leave normal flow and how the browser decides what paints on top of what. Interviewers reach for this the moment a modal, dropdown, or sticky header comes up, because z-index almost never behaves the way candidates expect.

part ofCSSoverview, primer and where to startread it →
on this pageshow

explore

questions

26

In CSS, how do position: relative and position: absolute differ in their effect on normal flow, and what are their top/left offsets measured from?

level: juniorimportance: must knowfreq 82%

answer

  1. one keeps its space, one does not
  2. layout first, then shift
  3. search upward for a positioned ancestor
  4. padding box of that ancestor
  5. shrink-to-fit once out of flow

basics

~20 s

A relatively positioned box keeps its space in normal flow and is only shifted visually from where it would have been. An absolutely positioned box is removed from flow entirely and offset from its containing block instead.

solid answer

~40 s

With `position: relative` the box still participates in normal flow — the layout is computed as if it had not moved, its original space stays reserved, and `top`/`left` then paint-shift it from that spot, so it can overlap neighbours without pushing them. With `position: absolute` the box is taken out of flow: following siblings close up as though it did not exist, its width shrink-to-fits its content instead of filling the parent, and its offsets are measured from its containing block — the padding box of the nearest ancestor whose `position` is not `static`, or the initial containing block if there is none. That asymmetry is why the classic pattern is `position: relative` on a wrapper purely to become the anchor, with `position: absolute` on the child that needs to sit in a corner.

code

css · 15 lines
css
.card {
  position: relative;
  padding: 16px;
}

.card .badge {
  position: absolute;
  top: 0;
  right: 0;
}

.nudged {
  position: relative;
  top: 10px;
}

go deeper

for a junior

Be ready to say plainly that relative keeps its space and shifts from where it was, while absolute leaves the flow and offsets from the nearest positioned ancestor. Naming the relative-wrapper plus absolute-child pattern is expected.

for a middle

Explain the mechanics: layout runs first and relative offsets are applied afterwards, absolute boxes shrink-to-fit, and the anchor search walks up for a non-static ancestor and uses its padding box.

for a senior

Show the debugging instinct — when something lands in the page corner, you look for the missing positioned ancestor; when a container collapses, you check whether all its children left the flow.

for a principal

Own the guidance on when absolute positioning is the wrong reach at all: it takes an element out of the flow that flex and grid manage, so reserve it for genuine overlays and anchors rather than as a general layout tool teams then have to maintain.

## Start with normal flow By default every element is `position: static`, which means it sits in normal flow: block boxes stack top to bottom in their parent's content box, inline boxes run along a line. A static box ignores `top`, `right`, `bottom`, `left` and `inset` completely — setting them changes nothing. The `position` property is what makes those offset properties mean anything. ## position: relative — shifted, but still occupying its space A relatively positioned element is laid out exactly as if it were static. Only after layout is finished are the offsets applied as a visual shift. Two consequences follow directly from that ordering: - **Its original space is preserved.** Neighbours do not move to fill the gap, so a shifted box leaves a hole behind it and may overlap whatever it moves toward. - **Offsets are measured from its own normal-flow position**, not from any ancestor. `top: 10px` means "10px down from where you would have been". Offsets on a relative box are also self-consistent by rule: if you specify both `top` and `bottom`, `bottom` is ignored; if you specify both `left` and `right` in a left-to-right writing mode, `right` is ignored. The box cannot be stretched — it can only be moved. Two side effects matter more in practice than the shifting itself. First, a relatively positioned element becomes a containing block for absolutely positioned descendants. Second, being positioned makes `z-index` apply to it, and a positioned element with a `z-index` other than `auto` establishes a stacking context. This is why `position: relative` with no offsets at all is such a common declaration — it is being used purely as an anchor. ## position: absolute — out of flow, anchored to a containing block An absolutely positioned element is removed from normal flow. Its parent no longer accounts for it when sizing itself, and later siblings lay out as if it were not in the document. Its own sizing changes too: `width: auto` no longer means "fill the parent" but shrink-to-fit, so an out-of-flow box collapses to its content width unless you give it a width or opposing insets. Its offsets resolve against the **containing block**: the padding box of the nearest ancestor whose `position` is anything other than `static` — `relative`, `absolute`, `fixed` or `sticky`. If no ancestor qualifies, the containing block is the initial containing block, a viewport-sized rectangle anchored at the document origin, so the box lands relative to the top of the *document* and scrolls away with the page. ```css .card { position: relative; } /* anchor only, no offsets needed */ .card .badge { position: absolute; top: 0; right: 0; /* corner of .card's padding box */ } ``` If you delete `position: relative` from `.card`, the badge does not fall back to the parent — it jumps to the corner of the page, because the parent was never in the running once it was static. ## The pairing that makes this the most-used pattern in CSS Because absolute positioning searches *upward* for a positioned ancestor, you control the anchor by choosing where to put `position: relative`. Badge on a card, caption over an image, dropdown under a button, custom focus ring around a control — all the same shape: relative wrapper, absolute child. ## Where people get burned A relative shift does not reflow anything, so nudging an element with `top` to "fix" spacing leaves the old space behind and usually should have been a margin. Conversely, an absolutely positioned element contributes nothing to its parent's height, so a container holding only absolute children collapses to zero height unless it is given one. ## The rest of the family, in one line each `position: fixed` is like absolute but its containing block is the viewport, so it does not scroll. `position: sticky` stays in flow and behaves like relative until a scroll threshold you define with an inset is crossed. Both are out of scope for the relative-versus-absolute comparison, but knowing that the difference between all five values is mostly *what they are positioned against* is the point of the family.

  • If a container holds only absolutely positioned children, why does it end up with zero height?
    Out-of-flow boxes do not contribute to their parent's content height. The parent sizes itself from its in-flow children only, and there are none, so it collapses. You have to give the container a height, an `aspect-ratio`, or some in-flow content — the absolute children will never push it open.
  • What happens if you set both top and bottom on a relatively positioned element?
    `bottom` is ignored. A relative box cannot be stretched by opposing offsets the way an absolute one can, because its size was already resolved in normal flow; the offsets only shift it. In a horizontal, left-to-right writing mode `top` wins over `bottom` and `left` wins over `right`.
  • Why do people write position: relative with no offset values at all?
    To make the element a containing block for absolutely positioned descendants, so a child with `inset` values anchors to it rather than to a further ancestor or the page. It also makes `z-index` apply to that element, which is the other common reason.

Relative is sliding a book along a shelf while its slot stays empty; absolute is taking the book off the shelf entirely and pinning it to the frame of the bookcase.

saying these in an interview costs you the question

  • Says relative elements are removed from the flow like absolute ones
  • Assumes an absolute child anchors to its direct parent automatically
  • Thinks absolute offsets are measured from the viewport
  • Believes top/left work on a default static element
  • Expects an absolutely positioned child to stretch the parent's height

context

open as a page

In CSS, how does position: sticky differ from position: fixed for a page header?

level: juniorimportance: must knowfreq 70%

basics

~20 s

position: sticky keeps the element in normal flow and pins it only after scrolling reaches the inset threshold you declared, inside its nearest scrolling ancestor. position: fixed removes the element from flow and pins it to the viewport from the start.

open as a page

In CSS, on which elements does the z-index property actually have an effect?

level: juniorimportance: must knowfreq 70%

basics

~20 s

z-index applies to positioned elements — those with position relative, absolute, fixed, or sticky — and also to flex items and grid items, which honour z-index even while position stays static. On any other element the declaration is ignored.

open as a page

In CSS, why does a container whose children are all floated collapse to zero height, and what makes it contain them again?

level: middleimportance: must knowfreq 62%

basics

~20 s

A block container with auto height measures only its in-flow children, and floats are out of flow, so it collapses. Make it contain floats by giving it display: flow-root, or add a cleared element after the floats.

open as a page

A dropdown panel positioned with `position: absolute` is cut off at the edge of a card whose CSS sets `overflow: hidden`. What decides whether an ancestor's overflow clips a positioned descendant, and what are the ways out?

level: middleimportance: must knowfreq 56%

basics

~20 s

An element with clipped overflow clips only descendants whose containing block is that element or something inside it. A positioned panel whose containing block is higher up escapes the clip — which is why position: fixed, or mounting the panel at the document root, fixes it.

open as a page

A modal styled `position: fixed; inset: 0` covers only part of the page and scrolls away with the content instead of staying pinned to the viewport. Its ancestor card declares `transform: translateZ(0)`. Why does the transform break the modal?

level: middleimportance: must knowfreq 64%

basics

~20 s

A transformed ancestor becomes the containing block for fixed-position descendants, so the modal is laid out against that card's padding box rather than the viewport. Move the overlay out of the transformed subtree, or drop the transform.

open as a page

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?

level: middleimportance: must knowfreq 70%

basics

~20 s

The 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.

open as a page

Why does an element declared position: sticky with no top, right, bottom, or left value never stick?

level: middleimportance: must knowfreq 60%

basics

~20 s

An inset value is what defines the threshold a sticky box must hold. With every inset left at its default of auto there is no constraint to enforce, so the box stays at its normal-flow position and behaves exactly like position: relative.

open as a page

In CSS, why can an element with z-index: 9999 still paint behind an element with z-index: 1?

level: middleimportance: must knowfreq 78%

basics

~20 s

z-index only orders elements within a single stacking context. If an ancestor of the 9999 element forms a stacking context that paints below the other element, the whole subtree paints with that ancestor, and no z-index value can escape it.

open as a page

In CSS, what does the clear property do, and which floats does it actually respond to?

level: juniorimportance: should knowfreq 48%

basics

~20 s

clear pushes a block box down until its top edge is below the bottom margin edge of earlier floats on the given side. It responds only to floats that precede it in the same block formatting context, and only to the sides you name.

open as a page

In CSS, when you set float: left on an image, what happens to the block boxes that follow it and to the text inside them?

level: middleimportance: should knowfreq 50%

basics

~20 s

A float is only partly removed from flow. Following block boxes are laid out as if it were not there, so their backgrounds and borders slide underneath it, but the line boxes inside them shorten to avoid the float's margin box, which is what makes text wrap.

open as a page

What does it mean for an element to render in the browser's top layer — as a `<dialog>` opened with `showModal()` does — and how does that change z-index, clipping, and its containing block?

level: middleimportance: should knowfreq 46%

basics

~20 s

The top layer is a viewport-sized painting layer above the whole document. An element promoted into it paints over all page content whatever their z-index values, is not clipped by ancestor overflow, and is positioned against the viewport rather than a transformed ancestor.

open as a page

Where does an element with position: absolute render if you leave top, right, bottom and left at their default value of auto?

level: middleimportance: should knowfreq 44%

basics

~20 s

It renders at its static position — the spot where its box would have started in normal flow — but out of flow, so it reserves no space and later siblings move up. Each axis is independent: an auto inset keeps the static position on that axis only.

open as a page

What width does an absolutely positioned element take when both left: 0 and right: 0 are set and width is auto, and what happens when a width is also specified?

level: middleimportance: should knowfreq 40%

basics

~20 s

With opposing insets and width: auto, the box stretches to span the containing block minus those insets and its margins. Adding an explicit width over-constrains the equation, so the browser ignores right in a left-to-right writing mode — unless both horizontal margins are auto, which centres the box.

open as a page

A sidebar with position: sticky and top: 0 follows the scroll for a while, then slides off the top of the screen. Why?

level: middleimportance: should knowfreq 48%

basics

~20 s

A sticky box can never be shifted outside its containing block. Once the parent's bottom edge reaches the threshold, the box stops holding position and scrolls away with the parent, so it only sticks for as long as its parent is on screen.

open as a page

In CSS, if no element on the page declares a z-index, in what order does the browser paint overlapping boxes?

level: middleimportance: should knowfreq 40%

basics

~20 s

Within 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.

open as a page

You inherit a stylesheet whose page grid is built from floated columns and clearfix rows. Why did float-based layout lose to flex and grid, and what would you check before converting it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Float was designed for wrapping text around an image, not for columns, so float grids need width maths, containment hacks, and source-order tricks, and still cannot do equal heights, gaps, or centring. Convert them to grid or flex, keeping float only where content genuinely wraps.

open as a page

An element with position: fixed normally resolves against the viewport. Which properties on an ancestor element make that ancestor the containing block instead, and what is the visible effect?

level: seniorimportance: should knowfreq 46%

basics

~20 s

An ancestor with transform, translate, rotate, scale, perspective, filter, backdrop-filter, will-change naming one of those, contain: layout/paint/content/strict, or a container-type becomes the containing block for fixed descendants. The fixed element then scrolls with that ancestor instead of staying put.

open as a page

A site header styled position: sticky; top: 0 renders fine but never pins when the page scrolls. How do you diagnose it?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Almost always an ancestor turned into a scroll container. Any ancestor with overflow hidden, auto or scroll captures the sticky element, which then sticks inside that box instead of the page — and if that box never scrolls, nothing ever happens.

open as a page

In CSS, an overlay stopped painting above the page after an ancestor got a transform. Which common declarations create a stacking context even with no z-index set?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Many declarations create a stacking context on their own: opacity below 1, any transform, filter or backdrop-filter, perspective, clip-path, mask, mix-blend-mode other than normal, isolation: isolate, contain values including paint, and will-change naming any of these. position: fixed does too.

open as a page

In a large application, modals, dropdowns, and toasts keep escalating their z-index values to fight each other, and new overlays regularly appear behind existing chrome. How would you restructure the overlay layer so this stops recurring?

level: principalimportance: should knowfreq 34%

basics

~20 s

Stop letting overlays compete where they are authored. Mount them in one root-level container or the browser top layer, define a small ordered scale of named layers, and forbid arbitrary z-index values in components — the escalation is a structural problem, not a numbering one.

open as a page

In CSS, what does the `::backdrop` pseudo-element style, and which elements have one?

level: juniorimportance: nice to knowfreq 30%

basics

~20 s

::backdrop is a browser-generated box painted immediately behind an element that is rendering in the top layer — a modal <dialog>, a shown popover, or a fullscreen element — covering the whole viewport. It is where you style the dimming scrim.

open as a page

In CSS, what does shape-outside do, and what must be true of an element for it to have any effect?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

shape-outside changes the contour that inline content wraps around, so text can follow a circle, polygon, or image alpha instead of a rectangle. It only applies to floated elements, and it changes the wrap, not the element's own painted shape.

open as a page

What problem does CSS anchor positioning (`anchor-name`, `position-anchor`, the `anchor()` function) solve for tooltips and popovers, and how would you ship it while support is uneven?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Anchor positioning lets an absolutely or fixed positioned overlay resolve its offsets from another element's box instead of its containing block, so a tooltip stays attached to its trigger without JavaScript measurement — including when the overlay is in the top layer.

open as a page

How do you build a table whose header row stays visible while the table body scrolls, using position: sticky?

level: seniorimportance: nice to knowfreq 32%

basics

~20 s

Apply position: sticky with top: 0 to the header cells and give them an opaque background, since rows scroll underneath. With border-collapse: collapse the borders scroll away, so use border-collapse: separate and draw the dividing line with box-shadow.

open as a page

How do you stop z-index values from escalating into an arms race across a large CSS codebase, and what role does isolation: isolate play?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

Treat z-index as a small, named set of page-level layers rather than free integers, and give each component root isolation: isolate so its internal values stay internal. Escalation happens only when every component competes in one global context.

open as a page