Explain the difference between static, relative, absolute, fixed, and sticky positioning
Advertisement
🧩 Scenario
Architecture Walkthrough
In Flow Versus Out of Flow
The first axis that separates the five values is whether the element still occupies space in normal flow.
static, relative, and sticky stay in flow. Their siblings lay out around them as if nothing special happened, and the space they reserve is based on their original position.
absolute and fixed are out of flow. They are removed from the normal layout pass entirely, so siblings collapse into the space they used to occupy. This is why an absolutely positioned element cannot push anything and cannot be pushed, and why a parent whose only children are absolutely positioned collapses to zero height.
What Each One Is Offset From
The second axis is the containing block: the rectangle that top, right, bottom, and left are measured against.
static is the default. Offsets and z-index are ignored entirely; the element sits where the flow puts it.
relative keeps the element in flow and offsets it visually from its own original position. Critically, the space it originally occupied stays reserved, so shifting an element down by 20px leaves a 20px gap where it used to be and may overlap whatever is below. Nothing else in the layout moves.
absolute removes the element from flow and offsets it from the padding box of its nearest positioned ancestor, meaning the nearest ancestor whose position is anything other than static. If no such ancestor exists, the containing block is the initial containing block, which is viewport-sized. That fallback is the source of the classic bug where a close button intended for a modal corner lands in the page corner instead.
fixed removes the element from flow and offsets it from the viewport, so it does not move when the page scrolls. The important caveat is that this is only true while no ancestor establishes a containing block for fixed descendants. An ancestor with transform, filter, backdrop-filter, perspective, contain: paint, or will-change on those properties captures fixed children, which makes them scroll with that ancestor instead of staying pinned to the viewport.
sticky is a hybrid. The element stays in flow and behaves exactly like relative until its offset threshold is crossed during scroll, at which point it stays pinned like fixed, but only within the bounds of its parent. When the parent scrolls past, the sticky element leaves with it. Because it stays in flow, no gap appears and no layout shift occurs when it sticks, which is its main advantage over the older "swap to fixed on scroll" pattern.
What relative Does Even With No Offsets
position: relative with no top, right, bottom, or left looks like a no-op but is not. It does two things.
First, it makes the element a positioned ancestor, so absolutely positioned descendants anchor to it. This is the standard container-plus-badge pattern and by far the most common reason to write it.
Second, combined with a z-index value other than auto, it creates a stacking context, which changes how descendants paint relative to the rest of the page.
When Not to Use Positioning at All
A large share of positioning in older codebases exists only to centre or align things, work that flexbox and grid now do more robustly. position: absolute with top: 50% and a translate(-50%, -50%) still has its place for overlays that must not affect layout, but for centring a child inside a container, display: grid; place-items: center is shorter, does not remove the child from flow, and survives content changes without recalculation.
Key Code Explained
/* static: the default. Offsets and z-index are IGNORED. */
.default {
position: static;
top: 50px; /* no effect whatsoever */
}
/* relative: stays in flow, offset from its OWN original position.
The original space stays reserved -> a 20px gap is left behind. */
.nudged {
position: relative;
top: 20px;
left: 10px;
}
/* The container/badge pattern: relative with NO offsets,
purely to become the containing block for the absolute child. */
.avatar {
position: relative;
}
.avatar .badge {
position: absolute;
top: -4px;
right: -4px;
/* offset from .avatar's padding box.
Without .avatar being positioned, this would anchor to the viewport. */
}
/* absolute: out of flow. Stretch to fill by setting all four offsets. */
.overlay {
position: absolute;
inset: 0; /* shorthand for top/right/bottom/left: 0 */
background: rgb(0 0 0 / 0.5);
}
/* fixed: offset from the viewport, immune to scrolling */
.fab {
position: fixed;
bottom: 24px;
right: 24px;
}
/* ...unless an ancestor creates a containing block for fixed descendants */
.broken-ancestor {
transform: translateZ(0); /* now .fab scrolls with THIS element */
}
/* sticky: relative until the threshold, then pinned within the parent.
The threshold offset is REQUIRED — sticky with no offset never sticks. */
.section-header {
position: sticky;
top: 0;
}
/* Modern alternative to absolute centring */
.centered-modern {
display: grid;
place-items: center; /* child stays in flow, no translate needed */
}
The .avatar and .avatar .badge pair is the most important idiom in this list. It shows both halves of the relationship: the child needs absolute to escape flow and sit in the corner, and the parent needs relative purely to be the thing that corner is measured from. Remove the parent's relative and the badge jumps to the page corner, which is the single most common absolute-positioning bug.
The .broken-ancestor rule is the detail that separates a working mental model from a memorised one. fixed is described everywhere as "relative to the viewport," but a transform anywhere up the ancestor chain quietly changes that, and the resulting behaviour, a fixed element that scrolls, looks impossible until you know the rule.
Tradeoffs
| Value | In flow | Offset from | Scrolls with page | Honours z-index |
|---|---|---|---|---|
static | Yes | Nothing; offsets ignored | Yes | No |
relative | Yes, original space reserved | Its own original position | Yes | Yes |
absolute | No | Nearest positioned ancestor's padding box | Yes, with that ancestor | Yes |
fixed | No | Viewport, unless captured by a transformed ancestor | No | Yes |
sticky | Yes | Nearest scrolling ancestor, bounded by its parent | Until threshold, then pinned | Yes |
What Interviewers Actually Check
- Whether you can say which values remove the element from flow and what that does to its siblings
- Whether you can name the containing block for
absolute,fixed, andrelativeprecisely - Whether you know
relativeoffsets leave the original space reserved - Whether you can explain what
position: relativeaccomplishes with no offsets at all - Whether you know a transformed ancestor breaks
fixed, rather than describingfixedas unconditionally viewport-relative
Follow-Up Questions
- What does
insetdo, and how does it relate to the four individual offset properties and their logical equivalents? - If an absolutely positioned element sets both
left: 0andright: 0but nowidth, what width does it take? - How does
position: absoluteinteract withdisplay: flexon the parent? Is the element still a flex item? - Why does a parent containing only absolutely positioned children collapse to zero height, and what are two ways to fix it?
- What problem do CSS anchor positioning and
position-areasolve thatabsolutepositioning alone could not?
Common Candidate Mistakes
- Forgetting that
position: relativereserves the element's original space, then being surprised by a gap above and an overlap below after nudging it - Writing
position: absolutewithout checking that an ancestor is positioned, so the element silently anchors to the initial containing block and lands in a page corner - Describing
fixedas always viewport-relative, missing that atransform,filter, orwill-changeon any ancestor captures it and makes it scroll - Treating
stickyas a flavour offixed, which fails to explain why it stops at its parent's boundary and why it causes no layout shift - Reaching for absolute positioning to centre or align content when
place-items: centeror flexbox alignment keeps the element in flow and survives content changes
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you say which of the five values remove the element from normal flow?
- Can you name the containing block for
absolute,fixed, andrelative? - Can you explain why
relativeoffsets leave a gap behind? - Can you explain what
position: relativeenables when no offsets are set? - Can you pick the right value for a badge, a full-screen modal, a pinned header, and a sidebar, and justify each?
Summary
The five position values divide first by flow participation. static, relative, and sticky remain in normal flow and keep reserving space; absolute and fixed are removed from flow, so siblings close up behind them and they can neither push nor be pushed.
They divide second by containing block. static ignores offsets completely. relative offsets from the element's own original position while leaving that space reserved, which is why nudging it creates a gap and an overlap rather than moving neighbours. absolute offsets from the padding box of the nearest non-static ancestor, falling back to a viewport-sized initial containing block when none exists. fixed offsets from the viewport, but only while no ancestor has a transform, filter, or similar property that captures fixed descendants. sticky behaves as relative until its threshold is crossed, then pins like fixed within its parent's bounds, staying in flow the whole time so nothing shifts.
Two practical points matter beyond the definitions. position: relative with no offsets is a real and deliberate declaration: it makes an element the anchor for absolute descendants and, with a z-index, creates a stacking context. And a great deal of legacy absolute positioning exists only to align things, work that place-items: center or flexbox now does without removing anything from flow.
What is a containing block?
The rectangle an offset element is positioned against. For absolute it is the padding box of the nearest positioned ancestor; for fixed it is normally the viewport; for relative it is the element own original position.
Why does position: relative with no offsets still change things?
Because it makes the element a positioned ancestor for absolute descendants, and it creates a stacking context once z-index is set. Both effects apply even with no top, right, bottom, or left.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement