~/TechPurAI
~/tutorials/css-from-scratch/positioning-static-relative-absolute-fixed-sticky
intermediate·part 13 of 22·4 min read

Positioning: static, relative, absolute, fixed, and sticky

Updated Aug 16, 2026CSS

Every layout so far has used normal document flow, Flexbox, or Grid — all genuinely collaborative systems where elements affect each other's position. position is different: it's how one specific element gets pulled out of that shared flow, positioned independently. This part covers all five real values, plus the containing-block mechanism that governs the last three.

static: the real, silent default

Every element is position: static unless explicitly set otherwise — normal document flow, exactly as covered since part 8. The top, right, bottom, and left properties have no effect at all on a statically positioned element; they only start doing anything once one of the other four values is set.

relative: a real, small nudge, without leaving the flow

css
.badge {
  position: relative;
  top: -4px;
}

position: relative keeps an element in normal document flow — it still occupies its real, original space, and other elements still position around it as if it hadn't moved — but then visually offsets it from that original position using top/right/bottom/left. Its real, second major use is covered next: it's what establishes a containing block for an absolutely positioned child.

absolute: real positioning relative to the nearest positioned ancestor

css
.product-card {
  position: relative;
}

.sale-badge {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
}

An absolutely positioned element is removed from normal document flow entirely — other elements lay out as if it doesn't exist — and positioned relative to its nearest ancestor that has any position value other than static. This is exactly why .product-card above needs position: relative even though it isn't visually offset by any top/left value of its own — that single declaration is what makes it the real reference point .sale-badge positions against, rather than the badge jumping all the way to the corner of the entire page.

Why it matters

This containing-block mechanism is exactly the real bug class flagged back in part 7's filter caveat — transform, filter, and a few other properties also create a new containing block as a real side effect, even without setting position at all. An absolutely positioned element nested inside an ancestor with any of these properties can end up positioned relative to that ancestor unexpectedly, instead of the one further up the tree it was actually meant to reference.

fixed: real positioning relative to the viewport, always

css
.back-to-top {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
}

position: fixed positions an element relative to the real browser viewport itself, staying in the same real screen position even as the page scrolls — genuinely useful for a persistent element like a "back to top" button. The same containing-block caveat from the callout above applies here too: a fixed element nested inside an ancestor with transform or filter set becomes positioned relative to that ancestor instead of the real viewport, which is a documented, real CSS behavior, not a bug — but a genuinely surprising one the first time it's encountered.

sticky: Bright Leaf Coffee's real header, done properly

css
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
}

position: sticky is a real, genuine hybrid — the element behaves like relative (staying in normal flow) until the page scrolls to the point where it would go past the specified top value, at which point it behaves like fixed, sticking in place. This is exactly the real mechanism behind Bright Leaf Coffee's sticky header: it scrolls normally with the page content until it reaches the very top of the viewport, then stays pinned there for the rest of the scroll.

z-index: real, only meaningful for positioned elements

css
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
}

z-index controls real stacking order — which element renders on top when two overlap — but it only has any effect on an element that already has a position value other than static. A common, real point of confusion is setting z-index on a static element and getting no visible effect at all, simply because the property has no defined meaning without a real positioning context to apply it in.

Next: responsive design — real media queries, the mobile-first approach, and making every page built across this series genuinely work at every real screen size.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous12. CSS Grid deep dive: a real responsive product grid with zero media queriesnext →14. Responsive design: media queries and the mobile-first approach