Position Values
| Value | In Flow? | Positioned? | Relative to |
|---|---|---|---|
| static | Yes | No | N/A — normal flow |
| relative | Yes | Yes | Its normal position |
| absolute | No | Yes | Nearest positioned ancestor |
| fixed | No | Yes | Viewport |
| sticky | Yes (until threshold) | Yes (after threshold) | Scroll container |
Offset Properties
Only work when position is not static:
/* All four offsets */
top: 0;
right: 0;
bottom: 0;
left: 0;
/* Common patterns */
.overlay { position: fixed; inset: 0; } /* inset: top right bottom left shorthand */
.badge { position: absolute; top: -8px; right: -8px; }
.sticky-nav { position: sticky; top: 0; }
The Containing Block
An absolute element is positioned relative to its nearest positioned ancestor (position other than static). This is one of the most common sources of confusion in CSS:
/* Pattern: position relative on parent to contain absolute children */
.card {
position: relative; /* ← this makes it the containing block */
}
.card .badge {
position: absolute;
top: 12px;
right: 12px;
/* positions relative to .card, not the page */
}
⚠️ transform creates a containing block too. If an ancestor has any transform applied, even
transform: none in some browsers, it becomes the containing block for absolutely positioned descendants — even if it's not position: relative. This can break fixed positioning.How position: sticky Works
/* Sticks to top of viewport when scrolled to */
.nav {
position: sticky;
top: 0; /* required — defines where it sticks */
z-index: 100;
}
/* Sticks to bottom (less common) */
.bottom-bar {
position: sticky;
bottom: 0;
}
Common reasons sticky doesn't work:
- No
top/bottomvalue set - Parent or ancestor has
overflow: hidden/auto/scroll - Element doesn't have enough room to scroll (parent isn't taller than it)