Episode 1 — Fundamentals / 1.8 — CSS Layout Mastery
1.8.e — Positioning
In one sentence: CSS
positionremoves elements from (or offsets them within) normal flow —relativeshifts without disrupting siblings,absoluteplaces relative to a positioned ancestor,fixedlocks to the viewport, andstickycreates scroll-aware elements.
Navigation: ← 1.8.d — Combining Grid & Flex · 1.8.f — Stacking Context →
1. static — the default
Every element starts with position: static. Static elements follow normal flow — block elements stack vertically, inline elements flow horizontally. The top, right, bottom, left, and z-index properties have no effect on static elements.
2. relative — offset from normal position
.badge {
position: relative;
top: -8px;
left: 12px;
}
- The element is offset from where it would have been in normal flow.
- Surrounding elements are not affected — the original space is preserved.
- Creates a containing block for absolutely positioned descendants.
- Creates a new stacking context when
z-indexis set.
Normal flow: With position: relative; top: -8px; left: 12px;
┌──────┐ ┌──────┐
│ A │ │ A │
├──────┤ ├──────┤
│ B │ │ │ ← original space preserved
├──────┤ ├──────┤
│ C │ ┌──────┐
└──────┘ │ B │ ← visually shifted
└──────┘
├──────┤
│ C │ ← not affected
└──────┘
3. absolute — relative to positioned ancestor
.parent {
position: relative; /* establishes containing block */
}
.tooltip {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
}
- The element is removed from normal flow — siblings behave as if it does not exist.
- Positioned relative to the nearest ancestor that has
positionset to anything other thanstatic(the containing block). - If no positioned ancestor exists, it positions relative to the initial containing block (the viewport/html).
Finding the containing block
The browser walks up the DOM tree looking for the first ancestor with:
position: relative,absolute,fixed, orsticky- A
transform,filter, orperspectiveproperty (these also create containing blocks)
4. fixed — relative to the viewport
.floating-btn {
position: fixed;
bottom: 24px;
right: 24px;
}
- Removed from normal flow.
- Positioned relative to the viewport — stays in place during scrolling.
- Exception: if an ancestor has
transform,filter, orperspective, the fixed element positions relative to that ancestor instead. This is a common source of bugs.
5. sticky — hybrid of relative and fixed
.sticky-header {
position: sticky;
top: 0;
}
- Behaves like
relativeuntil the element reaches a scroll threshold (defined bytop,bottom, etc.), then behaves likefixedwithin its scrolling container. - Stays in normal flow — surrounding elements are not affected.
- Stops sticking when it reaches the end of its parent container.
Scrolling down:
Before threshold: At threshold: Past parent:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Header │ │ ▓▓ Sticky ▓▓ │ ◄── stuck at top
│ Content │ │ Content │ │ Content │
│ ▓▓ Sticky ▓▓ │ │ Content │ │ (parent │
│ Content │ │ Content │ │ ended) │
│ Content │ │ Content │ │ ▓▓ Sticky ▓▓ │ ◄── scrolls away
└──────────────┘ └──────────────┘ └──────────────┘
Sticky gotchas
overflow: hiddenoroverflow: autoon an ancestor can break sticky behavior (the sticky element's scrolling container becomes that ancestor).- Must set at least one threshold property (
top,bottom, etc.) — without it, sticky has no threshold to trigger.
6. Offset properties: top / right / bottom / left
| Position value | What offsets are relative to |
|---|---|
relative | Element's normal position |
absolute | Containing block edges |
fixed | Viewport edges |
sticky | Scroll threshold within scrolling container |
Setting both top and bottom (or left and right) on an absolutely positioned element can stretch it:
.overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
/* stretches to fill the containing block — same as inset: 0 */
}
The inset shorthand replaces all four:
.overlay { position: absolute; inset: 0; }
.partial { position: absolute; inset: 10px 20px; } /* top/bottom 10px, left/right 20px */
7. Practical examples
Sticky header
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: white;
}
Absolute badge on a card
.card {
position: relative; /* containing block */
}
.card .badge {
position: absolute;
top: 12px;
right: 12px;
background: red;
color: white;
border-radius: 50%;
padding: 4px 8px;
}
┌─────────────────────┐
│ [NEW] ◄─── absolute badge
│ │
│ Card content │
│ │
└─────────────────────┘
Fixed floating action button
.fab {
position: fixed;
bottom: 24px;
right: 24px;
width: 56px;
height: 56px;
border-radius: 50%;
z-index: 200;
}
Absolute overlay (full-cover)
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
8. Comparison table
| Value | In flow? | Offset relative to | Creates stacking context? | Use case |
|---|---|---|---|---|
static | Yes | N/A | No | Default |
relative | Yes | Own normal position | With z-index | Containing block, small offsets |
absolute | No | Positioned ancestor | With z-index | Tooltips, badges, dropdowns |
fixed | No | Viewport | Always | Sticky headers, FABs, modals |
sticky | Yes | Scroll threshold | Always | Scroll-aware headers, sidebars |
Explain-It Challenge
Explain without notes:
- What is a containing block, and how does the browser find it for an absolutely positioned element?
- Why might a
position: fixedelement stop being fixed relative to the viewport? - What makes
position: stickydifferent from just using JavaScript to toggle betweenrelativeandfixed?
Navigation: ← 1.8.d — Combining Grid & Flex · 1.8.f — Stacking Context →