CSS Position: Static, Relative, Absolute, Fixed and Sticky
Basics4 minInnovialabs editorial team
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.
| Value | In flow? | Offsets measured from | Typical use |
|---|---|---|---|
static | Yes | Offsets do not apply | The default |
relative | Yes, space is kept | Its own normal position | Small nudges, anchor for absolute |
absolute | No | Nearest positioned ancestor | Card badge, dropdown menu |
fixed | No | The viewport | Header, back-to-top button, chat widget |
sticky | Yes | Acts like relative until it hits a threshold | Sticky 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 withpositionother thanstatic. 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 hastransform,filterorperspective,fixedis measured from that ancestor and stops being fixed on scroll. - The same properties (
transform,filter) also make an ancestor the anchor forabsolute.
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/absolutetogether with az-indexother thanauto;position: fixedandsticky;opacitybelow 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:
- No threshold. Without
top(orbottom), sticky behaves like relative. - An ancestor has
overflow: hidden,autoorscroll. Sticky attaches to the nearest scrolling ancestor, not the window. If that ancestor does not actually scroll, nothing happens. - 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.
- 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/leftset — 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: autowith explicit dimensions, ortransform: translate(-50%, -50%).