position is the property in charge of stacking, floating, and pinning—yet everyone has had an element vanish off-screen the instant they set absolute. This article is a quick reference to the five values and how the reference point gets decided.
The five values at a glance
| Value | Its original space | Reference point | Mainly used for |
|---|---|---|---|
static | Kept | Can’t be offset (default) | Cancelling another value |
relative | Kept | Its own original position | Nudging / becoming a reference for absolute |
absolute | Removed | Nearest ancestor that isn’t static | Badges, text over images, a button top-right |
fixed | Removed | The viewport | Sticky headers, a floating button |
sticky | Kept | Switches as you scroll | Pinned headings, table headers |
The most important column is “is its original space kept, or removed?” When it’s removed (absolute, fixed), the space it occupied closes up and everything around it moves.
Switch values in the demo below to see the difference. The orange box is the one with position on it.
With relative, box 3 stays put; with absolute, box 3 slides left. That’s what “taken out of the flow” means.
Positioning with top / right / bottom / left
Setting position to anything but static unlocks these four.
.badge {
position: absolute;
top: 8px; /* 8px down from the reference's top */
right: 8px; /* 8px in from the reference's right */
}top: 8px means “8px down from the top edge”; right: 8px means “8px left from the right edge.” Remember it as inward from the side you name.
There’s also inset, which writes all four at once.
.overlay {
position: absolute;
inset: 0; /* top, right, bottom, left all 0 = fills the parent */
}inset: 0 is the shortest way to say “cover the parent exactly.” It’s the go-to for laying a translucent black over a photo.
How absolute decides its reference point
This is where position trips up the most people. An absolute element walks up its ancestors and uses the first one whose position isn’t static.
<div class="card"> <!-- position: relative -->
<img src="photo.jpg" alt="A photo">
<span class="badge">NEW</span> <!-- position: absolute -->
</div>.card {
position: relative; /* ← this declares the reference */
}
.badge {
position: absolute;
top: 8px;
right: 8px;
}Without position: relative on .card, the badge flies to the top right of the whole page. That’s the cause of “it went somewhere odd” essentially every time.
fixed—pin it to the screen
The reference becomes the screen itself, so it doesn’t move as you scroll.
.header {
position: fixed;
top: 0;
left: 0;
width: 100%;
}sticky—pin it partway
sticky is the halfway behaviour: it flows normally until scrolling brings it to the offset you named, then it pins.
.section-title {
position: sticky;
top: 0;
}The difference from fixed is that it never leaves its parent. Once the parent scrolls past, it scrolls away with it. That’s how you get “each section’s heading pins, and swaps at the next section” in one line.
z-index—the stacking order
Which element wins when things overlap is decided by z-index. Bigger numbers come forward.
.modal {
position: fixed;
z-index: 100;
}One prerequisite matters: z-index only works on elements whose position isn’t static. If it “does nothing,” set position first.
The other trap is stacking contexts. If an ancestor has z-index, transform, or opacity below 1, everything inside it only competes within that layer. z-index: 9999 on a child won’t help if the whole layer sits behind. When something “just won’t come forward,” walk up the ancestors.
Six recipes you can use as-is
A badge at the top right of an image
.card {
position: relative;
}
.badge {
position: absolute;
top: 8px;
right: 8px;
}A translucent black over a photo
.thumb {
position: relative;
}
.thumb::after {
content: "";
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.35);
}Centre an overlaid element exactly
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* pull back half its own size */
}top: 50% alone puts the element’s top-left corner at the centre. Pulling back half its own size with transform is the other half of the pair.
A header that survives scrolling
.header {
position: sticky;
top: 0;
z-index: 10;
}Using sticky instead of fixed means the content never slips underneath, so you don’t have to compensate with padding. For a pinned header, sticky is the easier choice.
A floating button at the bottom right
.fab {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 50;
}Freeze a table’s header row
thead th {
position: sticky;
top: 0;
background: #fff; /* a background is required, or the rows show through */
}Checklist when it isn’t working
top/leftdo nothing →positionis stillstatic. Setrelativeor another value firstabsoluteflies far away → addposition: relativeto the parent you meant as the referencestickywon’t move → did you writetop? does an ancestor haveoverflow? is the parent tall enough?z-indexdoes nothing → did you setposition? is an ancestor creating a stacking context?fixedwon’t pin to the screen → atransformorfilteron an ancestor makes that ancestor the reference instead- A horizontal scrollbar appeared → an
absoluteorfixedelement is sticking out past the screen. Revisit therightvalue
Summary
- The key difference among the five values is whether the original space is kept or removed
absolutemeasures from the nearest ancestor that isn’tstatic—writingrelativeon the parent is the other half of the pairstickyonly pins within its parent. An offset is required; watch ancestors’overflowz-indexonly works on elements withpositionset- For a pinned header,
stickybeatsfixed(nothing slips underneath)
position stops being scary the moment you can say where the reference point is. If something flies away, look at the parent first.