HTML & CSS · Lesson 9 of 13
Positioning, z-index and Overflow
Learn CSS position relative, absolute, fixed and sticky, how z-index and stacking contexts work, and how to control overflow and scrolling.
- Intermediate
- 16 min read
- 4 objectives
Before this lessonLesson 8: Typography, Colors and CSS Variables
What you will learn
- Use relative, absolute, fixed and sticky positioning correctly
- Understand containing blocks and the inset shorthand
- Debug z-index problems using stacking contexts
- Control overflow, scroll containers and text truncation
Your Progress
0 of 13 lessons 0%
- Lessons0 / 13
- Completed0
- Est. time left~ 4 hours
Create a free account to keep your progress on every device.
Tip: pressing Next marks this lesson complete automatically.
Flexbox and Grid handle the overall layout of a page. But some things need to break out of the normal flow: a badge in the corner of a card, a sticky header, a dropdown menu, a modal that covers everything. That is the job of the position property. It is also where the most confusing CSS bug lives: "I set z-index: 9999 and it is still behind". By the end of this lesson you will know why.
Normal flow and position: static
Every element starts as position: static, the default. It sits where the normal flow puts it, and the offset properties top, right, bottom, left and z-index have no effect.
position: relative
A relatively positioned element stays in the flow (its original space is kept) but can be nudged with offsets. On its own that is rarely useful. Its real purpose is to become the containing block for absolutely positioned children.
.nudge {
position: relative;
top: 2px; /* moves down 2px visually; neighbours do not move */
}position: absolute
An absolutely positioned element is removed from the flow (other elements act as if it is not there) and placed relative to its nearest positioned ancestor, meaning the nearest ancestor with any position other than static. If there is none, it is positioned relative to the page.
<article class="card">
<span class="badge">New</span>
<h3>Docker for Beginners</h3>
</article>.card {
position: relative; /* the anchor for the badge */
padding: 1.5rem;
}
.badge {
position: absolute;
top: 0.75rem;
right: 0.75rem;
}
/* inset is shorthand for top/right/bottom/left */
.overlay {
position: absolute;
inset: 0; /* stretch to cover the whole card */
background: rgb(0 0 0 / 40%);
}position: fixed
A fixed element is positioned relative to the viewport and stays put when the page scrolls. It is used for floating chat buttons, cookie banners and modal backdrops.
.chat-button {
position: fixed;
right: 1.5rem;
bottom: 1.5rem;
}
.modal-backdrop {
position: fixed;
inset: 0;
background: rgb(15 23 42 / 60%);
}One gotcha: if any ancestor has a transform, filter or perspective, the fixed element becomes positioned relative to that ancestor instead of the viewport. For modals, the native <dialog> element with showModal() avoids this, because it renders in a special top layer above everything.
position: sticky
Sticky is a hybrid: the element scrolls normally until it reaches the offset you set, then sticks there while its parent is still on screen. Perfect for headers, table header rows and section titles.
.site-header {
position: sticky;
top: 0; /* required: where it should stick */
background: white; /* otherwise content shows through */
z-index: 10;
}
thead th {
position: sticky;
top: 0;
}If sticky does not work, check two things: you set top (or another offset), and no ancestor has overflow: hidden or overflow: auto, which makes the sticky element stick inside that ancestor instead of the page.
z-index and stacking contexts
When positioned elements overlap, z-index decides which is on top: higher numbers are closer to you. It only works on positioned elements (and flex or grid children).
The catch is stacking contexts. Certain properties create a new, self-contained layer group: position with a z-index, opacity below 1, transform, filter, isolation: isolate and a few others. Children are stacked inside their context and can never escape it. A child with z-index: 9999 inside a parent with z-index: 1 is still below a sibling of that parent with z-index: 2.
.header { position: relative; z-index: 2; }
.content { position: relative; z-index: 1; }
/* This dropdown lives inside .content, so it is capped at .content's level */
.content .dropdown { position: absolute; z-index: 9999; }
/* Result: the dropdown still renders BELOW .header */The fix is to raise the parent's context, or move the dropdown out of it. A tidy habit is to keep a small scale of z-index values as variables instead of random numbers:
:root {
--z-dropdown: 100;
--z-sticky: 200;
--z-modal: 300;
--z-toast: 400;
}Overflow
When content is bigger than its box, overflow decides what happens.
.visible { overflow: visible; } /* default: content spills out */
.hidden { overflow: hidden; } /* clipped, no scrollbar */
.scroll { overflow: auto; } /* scrollbar only when needed */
.clip { overflow: clip; } /* like hidden, but does not create a scroll container */
.code-block { overflow-x: auto; } /* long code lines scroll sideways */
/* One-line truncation with an ellipsis */
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Multi-line truncation (supported in all modern browsers) */
.clamp-3 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}If your whole page scrolls sideways on a phone, some element is wider than the screen. Rather than hiding it with overflow-x: hidden on body (which also breaks sticky positioning), find the culprit in DevTools and fix its width.
Recap
- Relative keeps the element in flow and anchors absolute children; absolute leaves the flow and positions against the nearest positioned ancestor.
- Fixed sticks to the viewport (unless an ancestor has a transform); sticky scrolls until its offset, then sticks within its parent.
inset: 0covers the containing block in one line.- z-index only competes within a stacking context; opacity, transform and z-index on a positioned parent create new contexts.
- Use
overflow: autofor scroll areas,text-overflow: ellipsisfor truncation, and fix wide elements rather than hiding page overflow.
// Write your solution here
Finished reading? Mark this lesson complete to track your progress.
