AniUI Academy

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.

8 min read

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.

Try it yourself
Loading playground...

What to remember

  • static (default) ignores top/right/bottom/left entirely and stays in normal flow.
  • relative stays in flow but can be nudged, and establishes a positioning context for absolutely positioned descendants.
  • absolute leaves flow entirely and positions relative to the nearest ancestor with any position other than static — or the viewport-level initial containing block if none exists.
  • fixed positions relative to the viewport and stays put through scrolling.
  • sticky behaves as relative until a scroll threshold is crossed, then as fixed within its containing block — and overflow: hidden/auto on an ancestor can silently break it.
  • z-index only 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

up to 50
  1. 1.An absolutely positioned element has no positioned ancestor anywhere above it in the DOM. What is it positioned relative to?

  2. 2.Which position value keeps an element in normal document flow, while still letting top/left/etc. nudge it visually?

  3. 3.What is the well-known gotcha with position:sticky?

  4. 4.When does z-index have any effect on an element?

4 left to answer