CSS position: relative vs absolute vs fixed
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.
| Value | Positioned against | In normal flow? |
|---|---|---|
static | Nothing (the default) | Yes |
relative | Its own normal position | Yes — leaves a gap behind |
absolute | Nearest positioned ancestor | No |
fixed | The viewport | No |
sticky | Its scroll container, past a threshold | Yes |
#The pairing you use most
.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
.overlay { position: absolute; inset: 0; }Equivalent to setting all four of top, right, bottom, left to 0.
#sticky, and why it silently fails
.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.