Positioning and z-index
Most boxes sit in their assigned seats. Positioning lets you pin a note to the corner, stick a sign to the window, or let one box float above the rest.
So far, every box has taken its turn in line. The first one goes at the top, the next one below it, and words flow left to right. This is called normal flow, and it's how most of the page should work. It's predictable, and it adapts to any screen size.
But sometimes you need to break the line. A "SALE" badge in the corner of a product card. A header that stays at the top while you scroll. A dropdown menu that floats over the content below. For those, we use position.
A warning before we start: positioning is powerful and easy to misuse. People new to CSS often try to build entire layouts by positioning every box to exact coordinates, and it falls apart the moment the screen size changes. Use positioning for small, specific jobs. For whole layouts, flexbox and grid (coming up next) are the right tools.
Think of a classroom
Imagine a classroom where every student has an assigned seat. That's normal flow. Now let's look at the ways a student can end up somewhere else.
static: sitting in your seat
.box {
position: static;
}This is the default for every element. You're in your assigned seat. top, left, and friends do nothing at all. You'll rarely write this out, since it's already the case.
relative: leaning in your chair
.nudge {
position: relative;
top: 10px;
left: 20px;
}A relatively positioned box is shifted from where it would have been. It's like leaning over in your chair: you move a bit, but your seat is still yours. Nobody else sits in it, and nobody moves over to fill the gap.
Honestly, nudging things is the least common use of relative. Its real job is the next one: being an anchor for absolute positioning.
absolute: a sticky note on the whiteboard
.card {
position: relative; /* the anchor */
}
.badge {
position: absolute;
top: 12px;
right: 12px;
}An absolutely positioned box leaves its seat entirely. It's removed from the normal flow, so the other boxes close up the gap as if it weren't there. Then it's placed using top, right, bottom, and left, measured from the edges of its anchor.
What's the anchor? The nearest ancestor that has any position other than static. Think of a teacher sticking a note on a whiteboard: "12px from the top, 12px from the right" only makes sense once you know which whiteboard. Setting position: relative on the .card says "this card is the whiteboard."
Forget to set an anchor, and the browser keeps looking up the family tree until it reaches the page itself. That's the classic bug: your "corner badge" ends up in the corner of the whole page, far away from the card it belongs to. If an absolute element flies off somewhere strange, check its ancestors for a position: relative.
fixed: a sticker on the car window
.chat-button {
position: fixed;
bottom: 24px;
right: 24px;
}A fixed box is positioned relative to the browser window itself, and it stays there when you scroll. Think of a sticker on a car window: the scenery rushes past, but the sticker stays in the same spot on the glass.
It's great for a "chat with us" button or a cookie banner. Use it sparingly, though. Every fixed element permanently covers part of the screen, and on a small phone that space is precious.
sticky: a bookmark that catches
.site-header {
position: sticky;
top: 0;
}Sticky is a clever mix. The box sits in normal flow, in its seat, until you scroll it to the top value you set. Then it sticks there, like a bookmark ribbon catching at the top of the page, and it keeps sticking until its parent container scrolls out of view.
It's the modern way to build headers that stay visible while you scroll, and table headers that stay put in long tables. And unlike fixed, it still takes up its own space in the flow, so nothing is hidden underneath it at the start.
Two gotchas: sticky needs a top (or bottom) value to work, and it stops working if an ancestor has overflow: hidden. If your sticky header refuses to stick, check those two first.
inset: a handy shorthand
.overlay {
position: absolute;
inset: 0; /* top, right, bottom, and left all 0: fill the anchor */
}inset works like the padding shorthand from The Box Model, but for top, right, bottom, and left.
z-index: papers on a desk
When positioned boxes overlap, which one is on top?
.dropdown {
position: absolute;
z-index: 10;
}
.site-header {
position: sticky;
top: 0;
z-index: 100;
}Picture papers scattered on a desk. z-index is how high up in the pile each paper sits. Higher numbers are closer to you. It only works on positioned elements (and on flex and grid children).
Now, the part that drives people up the wall. You set z-index: 9999 on a dropdown, and it still hides behind the header. What?
It's because of stacking contexts. Imagine the papers aren't loose. Some of them are inside folders. A paper inside a folder can be at the very top of that folder's pile, but it can never rise above a different folder that's sitting on top of it. The folder decides.
Certain properties turn an element into a new "folder": a positioned element with a z-index, anything with opacity less than 1, transform, and a few others. So when z-index isn't working, don't just add more nines. Look up the family tree and find which folder your element is trapped in. Your browser's developer tools from Debugging Basics are the best way to hunt it down.
A tip to avoid the whole mess: keep z-index values small and deliberate, like 1, 10, and 100 for "a little above," "dropdowns," and "headers and dialogs."
Try it
Scroll inside the preview to see the sticky header in action.
CSS
Preview
Try it yourself
- Remove
position: relativefrom.cardand run it. Where does the SALE badge go? Put it back. - Move the badge to the bottom-left corner of the card.
- Change the header from
stickytofixedand scroll to the top. Notice how the fixed header now covers the start of the content, because it no longer takes up space? (It also shrinks to the width of its text, since a fixed box no longer stretches across its container. Addleft: 0; right: 0;to stretch it back.) - Change the header's
z-indexto-1and scroll. What slides over it?
Check your understanding
Up next
You've now got every tool for placing individual boxes. It's time for the tool that changed CSS layout forever: Flexbox: The Basics. If display still feels a bit shaky, Display is right behind you.
