Skip to content
Innovialabs

CSS Position: Static, Relative, Absolute, Fixed and Sticky

Basics4 min

In short

static is normal flow, relative shifts an element while keeping its space, absolute and fixed take it out of flow and position it from a positioned ancestor or the viewport, and sticky sticks on scroll within its parent.

The short answer

The position property decides two things: whether an element stays in normal flow and what top, right, bottom and left are measured from.

ValueIn flow?Offsets measured fromTypical use
staticYesOffsets do not applyThe default
relativeYes, space is keptIts own normal positionSmall nudges, anchor for absolute
absoluteNoNearest positioned ancestorCard badge, dropdown menu
fixedNoThe viewportHeader, back-to-top button, chat widget
stickyYesActs like relative until it hits a thresholdSticky table header, sidebar

Each value with an example

static. Elements follow one another like text in a document. top and z-index have no effect.

relative. The element moves visually, but its original space stays reserved — neighbors do not move.

.note { position: relative; top: 8px; left: 8px; }

The main practical use of relative is making an element an anchor for absolutely positioned children.

absolute. The element is removed from flow: neighbors close the gap as if it were not there. Offsets are measured from the nearest ancestor whose position is not static.

.card { position: relative; }
.card .badge { position: absolute; top: 12px; right: 12px; }

fixed. The element is pinned to the viewport and does not move on scroll. Content still runs underneath it, so a fixed header needs matching space reserved for it.

sticky. A hybrid: the element stays in flow until it reaches a threshold (say, top: 0), then sticks — but only within its parent.

.table-head { position: sticky; top: 0; }

Containing block: what offsets are measured from

The containing block is the rectangle that offsets and percentages are calculated against.

  • For absolute — the nearest ancestor with position other than static. If none exists, it is the initial containing block, and the element flies to a corner of the document.
  • For fixed — the viewport. But if any ancestor has transform, filter or perspective, fixed is measured from that ancestor and stops being fixed on scroll.
  • The same properties (transform, filter) also make an ancestor the anchor for absolute.

z-index and stacking contexts

z-index controls stacking order, but only on positioned elements (and on flex and grid items).

The key idea is the stacking context: a layer inside which z-index values are compared only with each other. A new context is created by, for example:

  • position: relative/absolute together with a z-index other than auto;
  • position: fixed and sticky;
  • opacity below 1, transform, filter;
  • isolation: isolate.

That is why a modal with z-index: 9999 can end up under the header: if it sits inside a parent with its own context and z-index: 1, while the header has z-index: 2, the child cannot rise above its parent. The fix is to move the modal closer to the document root or adjust the parents' z-index — not to make the number bigger.

Why sticky does not stick

Go through this list:

  1. No threshold. Without top (or bottom), sticky behaves like relative.
  2. An ancestor has overflow: hidden, auto or scroll. Sticky attaches to the nearest scrolling ancestor, not the window. If that ancestor does not actually scroll, nothing happens.
  3. The parent is no taller than the element. Sticky only moves within its parent. If the parent is the same height, there is no room to stick.
  4. The element is stretched in a flex container. In a flex row, items stretch to full height by default. Add align-self: flex-start.

Why absolute elements jump

  • The parent has no position: relative — offsets come from a more distant ancestor or the page.
  • No top/left set — the element stays where it would be in flow and moves with the surrounding content.
  • Percentages are based on the containing block, not the element itself. To center, use inset: 0; margin: auto with explicit dimensions, or transform: translate(-50%, -50%).

FAQ

relative stays in flow and shifts from its own position without affecting neighbors. absolute leaves the flow, neighbors take its place, and offsets are measured from the positioned ancestor.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.