Lesson 21 of 32
Positioning
static, relative, absolute, fixed and sticky positioning, what each is relative to, the nearest positioned ancestor rule for absolute, the sticky/overflow gotcha, and z-index.
Every layout technique so far — flow, flexbox, grid — decides where an element sits among its siblings. Positioning is different: it's about pulling an element partly or fully out of that normal arrangement and placing it relative to something else entirely — its own original spot, an ancestor, or the browser window itself.
position: static — the default
.box { position: static; }Every element starts here. Normal document flow, exactly as covered in
every layout lesson so far. top, right, bottom, and left have no
effect at all on a statically positioned element — they're simply ignored.
position: relative — nudged, but still in flow
.box {
position: relative;
top: 10px;
left: 20px;
}A relatively positioned element stays exactly where it would be in normal
flow — its original space is still fully reserved, and nothing around it
moves — but it can then be visually nudged away from that spot using top,
right, bottom, and left. The nudge is purely visual; the layout still
behaves as if the element were sitting in its original position.
relative has a second, arguably more important job: it establishes a
positioning context for descendants. Any absolutely positioned element
inside it will position relative to it, rather than skipping past it to
some ancestor further up. This is the reason you'll see position: relative
applied to plenty of elements that never actually use top/left
themselves — it's there purely to anchor an absolutely positioned child.
position: absolute — out of flow, relative to the nearest positioned ancestor
.parent {
position: relative; /* now the positioning context */
}
.badge {
position: absolute;
top: 8px;
right: 8px;
}An absolutely positioned element is removed from normal document flow
entirely — its siblings behave as if it isn't there, closing up any space
it would have taken. It's then positioned using top/right/bottom/
left, relative to the nearest ancestor whose position is anything
other than static — that's the precise rule: walk up the DOM from the
element, and the first ancestor you find with position: relative,
absolute, fixed, or sticky becomes the reference box.
If no such ancestor exists anywhere up the tree, the absolutely positioned element falls back to the initial containing block — which, for a normal document, is effectively the viewport at the root of the page. That's why an absolutely positioned element with no positioned ancestor often appears to jump to the top-left of the entire page rather than staying near where it was written in the HTML.
This is exactly why .parent { position: relative; } above matters: without
it, .badge's top/right would resolve against the viewport, not against
.parent, and it would end up nowhere near the corner of the card it's
meant to sit on.
position: fixed — relative to the viewport, always
.toolbar {
position: fixed;
top: 0;
left: 0;
right: 0;
}A fixed element is also removed from normal flow, but it positions relative
to the viewport specifically (not any ancestor), and it stays visually
locked in that spot even as the page scrolls underneath it. This is the tool
for a sticky top toolbar or a floating action button that should never move
regardless of scroll position. (There's a niche exception: certain CSS
properties on an ancestor, like transform, can turn that ancestor into the
containing block for a fixed descendant instead of the viewport — worth
knowing exists, not worth worrying about at this stage.)
position: sticky — relative, until it isn't
.section-header {
position: sticky;
top: 0;
}Sticky is a hybrid: the element behaves like position: relative — sitting
in normal flow — until the scroll position crosses the threshold you define
with top (or right/bottom/left). Past that point, it behaves like
position: fixed, but only within its containing block — once its
containing block scrolls fully out of view, the sticky element scrolls away
with it rather than sticking forever.
The real, well-known gotcha: overflow: hidden or overflow: auto (or
scroll) on an ancestor between the sticky element and its containing block
can silently break the sticky effect entirely. Sticky positioning needs
room to "stick" within a scrollable area, and an ancestor that clips or
creates its own separate scroll context can cut that off before it ever
gets the chance — the element simply behaves as if position: relative had
been used the whole time, with no error or warning that anything went
wrong. If a position: sticky element mysteriously refuses to stick, an
ancestor's overflow setting is the first thing worth checking.
z-index and stacking order
.modal {
position: fixed;
z-index: 100;
}z-index only has any effect on positioned elements — anything other
than static. Set it on a statically positioned element and it's simply
ignored, which is a common reason a z-index rule seems to do nothing.
Among positioned elements, a higher z-index stacks visually on top of a
lower one. This comparison only happens between elements that share the same
stacking context — a more advanced idea than this lesson goes into — but at
an introductory level, the practical rule holds: give the element you want
on top (a modal, a dropdown, a tooltip) a higher z-index than whatever it
needs to appear above.
Try it yourself
A card uses position: relative purely to anchor an absolutely positioned
badge in its corner. Below it, a sticky section header sticks within its own
scrollable panel until that panel scrolls past it — demonstrating sticky's
real, containing-block-scoped behaviour rather than a permanent stick.
What to remember
static(default) ignorestop/right/bottom/leftentirely and stays in normal flow.relativestays in flow but can be nudged, and establishes a positioning context for absolutely positioned descendants.absoluteleaves flow entirely and positions relative to the nearest ancestor with anypositionother thanstatic— or the viewport-level initial containing block if none exists.fixedpositions relative to the viewport and stays put through scrolling.stickybehaves as relative until a scroll threshold is crossed, then as fixed within its containing block — andoverflow: hidden/autoon an ancestor can silently break it.z-indexonly affects positioned elements, and only compares against other elements sharing the same stacking context.
Check yourself
4 questions · pass 3/4 to unlock Responsive Design and Media Queries
1.An absolutely positioned element has no positioned ancestor anywhere above it in the DOM. What is it positioned relative to?
2.Which position value keeps an element in normal document flow, while still letting top/left/etc. nudge it visually?
3.What is the well-known gotcha with position:sticky?
4.When does z-index have any effect on an element?
4 left to answer