CSS position: relative vs absolute vs fixed

Short answer

relative offsets an element from where it would normally sit, and creates a positioning context. absolute positions against the nearest positioned ancestor and leaves normal flow. fixed positions against the viewport and does not scroll.

ValuePositioned againstIn normal flow?
staticNothing (the default)Yes
relativeIts own normal positionYes — leaves a gap behind
absoluteNearest positioned ancestorNo
fixedThe viewportNo
stickyIts scroll container, past a thresholdYes

#The pairing you use most

css
.card { position: relative; }        /* the anchor */

.card__badge {
  position: absolute;
  top: 1rem;
  right: 1rem;
}

Without position: relative on the parent, the badge searches up the tree for the nearest ancestor that is not static — often ending at the page itself, which is why a badge sometimes appears in the top corner of the screen.

#inset is the shorthand

css
.overlay { position: absolute; inset: 0; }

Equivalent to setting all four of top, right, bottom, left to 0.

#sticky, and why it silently fails

css
.toc {
  position: sticky;
  top: 1rem;
}

Two rules people trip over:

It needs a threshold. Without top, bottom, left or right, sticky does nothing at all.

Its parent must be taller than it. Sticky elements stick within their parent. If the parent is exactly the element's height, there is no room to move and it never appears to stick.

Also: any ancestor with overflow: hidden or overflow: auto becomes the scroll container and breaks sticky behaviour relative to the page.

#fixed and transforms

A transform, filter or will-change on an ancestor creates a new containing block, which makes position: fixed position against that ancestor rather than the viewport. This is the cause of most "my fixed header is not fixed" reports.