CSS position: static, relative, absolute, fixed, sticky
CSS position has five values, and absolute is the one that catches everyone. Here is what each does to the box and the layout around it, measured.

The position property decides two things: whether an element stays in the normal flow, and what top, right, bottom and left are measured against.
| Value | In the flow? | Offsets measured from |
|---|---|---|
static |
Yes | Nothing, offsets are ignored |
relative |
Yes | Its own normal position |
absolute |
No | Nearest positioned ancestor |
fixed |
No | The viewport |
sticky |
Yes | Its scroll container, once a threshold is crossed |
Rows three and four are where the surprises live, because an element that leaves the flow stops affecting everything around it. Every number below was measured in a real browser.
static: the default, and offsets do nothing
Every element is static unless you say otherwise. Offsets are ignored entirely:
#stat {
position: static;
top: 50px;
left: 50px;
}
Measured, that element rendered at x: 0, y: 0. The top and left had no effect at all.
Here is the part worth knowing, because it wastes time:
getComputedStyle(el).top // "50px"
The computed style reports 50px for a property that is doing nothing. The value is set, it is readable, and the browser is ignoring it. So "I checked and top is 50px" is not evidence that positioning is working. Only the rendered box tells you that.
This is the same trap as z-index on a static element, and it is why the fix for "my top is not working" is almost always to add a position.
relative: offset, but the space stays
#rel {
position: relative;
top: 20px;
left: 30px;
}
Measured, the element moved to x: 30, y: 60, having started at y: 40. So it shifted by exactly the offsets given.
The important half is what happened to its neighbour. The element after it rendered at y: 80, which is where it would have been if nothing had moved. The gap the relative element left behind is still reserved.
That is the defining behaviour: relative moves the paint position and leaves the layout alone. Nothing else on the page shuffles up to fill the space, and the element can overlap its neighbours without pushing them.
Because of that, relative on its own is rarely what you want for layout. Its main job is the one in the next section.
absolute: out of the flow, and anchored to an ancestor
.anc { position: relative; }
#abs { position: absolute; top: 10px; left: 10px; }
Measured, the parent .anc sat at y: 320 and the absolute child rendered at y: 330, exactly 10px inside it. That is the pattern you will use most: a relative parent that positions nothing, existing only to give an absolute child something to anchor to.
An absolutely positioned element is removed from the flow. It takes up no space, its siblings behave as though it does not exist, and its width shrinks to fit its content unless you give it one.
The absolute that ignores its parent
This is the one that produces "my element flew to the corner of the page", and it is worth seeing measured.
Same markup, except the parent has no position declared:
.noanc { /* position: static, the default */ }
#abs2 { position: absolute; top: 10px; left: 10px; }
The parent rendered at y: 460. The absolute child rendered at y: 10.
It ignored its parent completely and positioned itself against the top of the page. absolute measures from the nearest positioned ancestor, meaning the nearest one whose position is anything other than static. If there is no such ancestor anywhere up the tree, it falls back to the initial containing block, which is effectively the document.
So the rule is: an absolutely positioned element needs a positioned parent, or it will escape. When something lands in the top-left corner of the page instead of inside its card, this is why, and the fix is one line on the ancestor:
.card { position: relative; }
Any non-static value works. relative is the conventional choice because it changes nothing else about the parent.
fixed: anchored to the viewport
#fix { position: fixed; top: 5px; left: 5px; }
Measured at x: 5, y: 5, and it stays there while the page scrolls. Like absolute, it is out of the flow and reserves no space.
fixed is what you want for a header that never moves, a cookie banner, or a floating action button. Two things to know.
It is measured from the viewport, not from any ancestor. No positioned parent is needed or consulted.
Except when an ancestor has a transform. A transform, filter, perspective, backdrop-filter or will-change on any ancestor creates a containing block for fixed descendants, and your fixed element will be measured from that ancestor instead of the viewport. It stops behaving like fixed and starts behaving like absolute.
That is the same set of properties that traps z-index inside a stacking context, and it catches people for the same reason: the property was added for a visual effect by someone not thinking about positioning. If a fixed header suddenly scrolls away, look for a transform on a wrapper.
sticky: relative until it is fixed
.stick { position: sticky; top: 0; }
sticky behaves as relative while the element is in view, and switches to behaving as fixed once you scroll past the threshold you set. It stays in the flow the whole time, so it reserves its space and does not overlap anything.
It has more failure modes than the other four combined, including one where a parent's overflow silently disables it. Those are covered in why position: sticky is not working.
What "out of the flow" actually costs
Both absolute and fixed remove an element from normal flow, and the consequences go further than most explanations suggest. Four of them matter in practice.
The parent no longer grows to contain it. A container whose only children are absolutely positioned has a height of zero, because there is nothing in the flow to give it height. This is the reason a card collapses when you position its contents, and the fix is either to give the container an explicit height or to leave at least one child in the flow.
Percentage sizes change meaning. A width: 50% on an absolutely positioned element is 50% of its positioned ancestor, not of its DOM parent. When those are different elements, the number is measured against something other than the box you can see.
Shrink-to-fit sizing. An absolutely positioned element with no width shrinks to its content rather than filling its parent, which is the opposite of a block element's default. That is why an absolutely positioned <div> often ends up much narrower than expected.
It does not scroll with overflow. An absolutely positioned element inside a scrolling container scrolls with it only if the container is its positioned ancestor. If the anchor is further up the tree, the element stays put while the content moves underneath it.
None of these is a bug. They follow from the element having left the layout, and they are the reason to reach for flex or grid first and use positioning for the things that genuinely sit on top.
Offsets, and what happens when you set opposing pairs
Setting one offset anchors that edge. Setting both edges of an axis does something different: it stretches the element between them.
.overlay {
position: absolute;
top: 0;
bottom: 0; /* both vertical edges: height is now implied */
left: 0;
right: 0; /* both horizontal edges: width is implied */
}
That is how inset: 0 fills a container without you declaring a width or a height, and it works because the two offsets plus the element's size have to add up to the containing block's size. Give it two edges and it solves for the size.
Add an explicit width alongside both horizontal offsets and something has to give. In that case right is ignored in a left-to-right document, because the specification has to break the tie somewhere.
This is worth knowing because it makes the overlay pattern predictable rather than magical, and it explains why adding a width to an inset: 0 element sometimes appears to do nothing to one edge.
Choosing, by what you are building
| Goal | Use |
|---|---|
| Nudge an element slightly | relative with offsets |
| Give an absolute child something to anchor to | relative with no offsets |
| Badge on the corner of a card | absolute inside a relative card |
| Overlay covering a container | absolute with all four offsets set to 0 |
| Header that never moves | fixed |
| Modal over the whole page | fixed, or the <dialog> element |
| Section heading that stays while its section scrolls | sticky |
The overlay pattern is worth spelling out, because it is the most useful thing absolute does:
.overlay {
position: absolute;
inset: 0;
}
inset: 0 is shorthand for all four offsets at zero, which stretches the element to fill its positioned ancestor exactly. Before inset existed you wrote all four properties out, and you will still see that in older code.
position and z-index
z-index only applies to positioned elements. On a static element it is ignored, and, exactly like top, the computed style still reports whatever number you set.
That is one of the two reasons a z-index appears to do nothing. The other is stacking contexts: certain properties on an ancestor, including opacity, transform and filter, create a self-contained layer, and a child can never paint above something outside its own layer no matter how high its number goes.
There is one exception worth knowing: flex and grid children honour z-index without a position, because being a flex or grid item is itself enough to make the element participate in stacking.
Centring something absolutely positioned
The pattern comes up constantly and has two forms worth knowing.
.centred {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
The offsets put the element's top-left corner at the centre, which is not the same as centring it. The transform then pulls it back by half its own width and height, and it works without knowing those dimensions because percentages in transform are relative to the element itself.
The modern alternative avoids the transform entirely:
.centred {
position: absolute;
inset: 0;
margin: auto;
width: 200px;
height: 100px;
}
Opposing offsets plus margin: auto distributes the leftover space evenly on both axes. It needs an explicit size, which the transform version does not, so the first form remains more common. Both are worth recognising, and neither is needed if flex or grid is already doing the layout.
Three common mistakes
Using absolute for layout. It is tempting because you can put things exactly where you want them, and it produces a layout that does not respond to content changes, text length or screen size. Absolute positioning is for things that sit on top of a layout: badges, overlays, tooltips, close buttons. The layout itself belongs to flex or grid, which is covered in flexbox vs grid.
Forgetting the positioned parent. The element escapes to the corner of the page. Add position: relative to the container.
Reading the computed style as proof. top, left and z-index all report their declared values on a static element while doing nothing at all. Trust the rendered box, or the box model panel in DevTools, over the computed values.
Quick reference
position: static; /* default, offsets ignored */
position: relative; /* offset from normal position, space kept */
position: absolute; /* out of flow, anchored to positioned ancestor */
position: fixed; /* out of flow, anchored to the viewport */
position: sticky; /* in flow, sticks past a threshold */
.parent { position: relative; } /* anchor for absolute children */
.overlay { position: absolute; inset: 0; } /* fill the parent */
Want to move boxes around and watch the layout react? Start with the CSS track, or read the complete flexbox guide for the layout mode that should be doing most of your positioning.
More from the blog

Flexbox vs Grid: how to choose in ten seconds
Flexbox vs grid: flex sizes items to their content, grid sizes the tracks. Measured, that gave 32px and 229px against three equal 193px columns.
Read more
CSS specificity: why your style is not applying
Specificity is three numbers compared left to right, not one score. Here are the contests that decide it, measured in a browser.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.