CSS Position
Control exactly where elements are placed on the page — static, relative, absolute, fixed, and sticky.
1. Introduction
By default, HTML elements flow naturally from top to bottom. But many layouts require elements to be placed in specific positions — a sticky navigation bar, a tooltip appearing next to a button, an overlay covering the entire screen, or a "Back to Top" button floating in the corner.
The CSS position property lets you take elements out of the normal flow and place them precisely. This lesson covers all five position values and when to use each.
Once you set a position other than static, you can use the offset properties top, right, bottom, left (and the shorthand inset) to move the element.
2. Theory
position: static (default)
Every element starts as static. It sits in the normal document flow. Offset properties (top, left, etc.) have no effect.
div { position: static; } /* default — no need to set this */
position: relative
The element stays in the normal flow (its original space is preserved), but you can shift it visually using offset properties. The shift is relative to its own normal position.
.nudge {
position: relative;
top: 10px; /* moves 10px down from where it would normally be */
left: 20px; /* moves 20px to the right */
}
/* The element still occupies its original space — other elements
are NOT affected by the visual shift */
Most important use of relative: It creates a positioning context for absolutely positioned children.
position: absolute
The element is removed from the normal flow — it no longer takes up space. It is positioned relative to its nearest positioned ancestor (an ancestor with position other than static). If none exists, it positions relative to the initial viewport.
.parent {
position: relative; /* creates positioning context */
width: 200px;
height: 200px;
}
.badge {
position: absolute;
top: 8px; /* 8px from parent's top edge */
right: 8px; /* 8px from parent's right edge */
/* This badge is anchored to .parent */
}
position: fixed
The element is removed from the flow and positioned relative to the viewport (browser window). It stays in the same screen position even when the page scrolls.
.sticky-header {
position: fixed;
top: 0;
left: 0;
right: 0; /* stretch across the full width */
/* or: width: 100%; */
z-index: 100; /* appear above other content */
background: white;
}
.back-to-top {
position: fixed;
bottom: 24px;
right: 24px; /* bottom-right corner of screen */
}
position: sticky
A hybrid of relative and fixed. The element behaves as relative until it reaches a specified scroll threshold, then "sticks" like fixed until its parent ends.
.table-header {
position: sticky;
top: 0; /* sticks when it reaches 0px from the top of the viewport */
background: white;
z-index: 10;
}
.sidebar {
position: sticky;
top: 80px; /* sticks 80px from top (below a fixed navbar) */
height: fit-content; /* important — don't let it stretch to parent height */
}
z-index — Stacking Order
When positioned elements overlap, z-index controls which appears on top. Higher value = on top. Default: auto (follows DOM order).
.modal-overlay { z-index: 1000; }
.modal-content { z-index: 1001; } /* above the overlay */
.tooltip { z-index: 9999; } /* always on top */
Important: z-index only works on positioned elements (not static).
The inset Shorthand
Modern shorthand for top + right + bottom + left.
/* These are equivalent */
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
position: absolute;
inset: 0; /* shorthand for all four = 0 */
Position Summary Table
| Value | In flow? | Positioned relative to | Common use |
|---|---|---|---|
static | Yes | — | Default |
relative | Yes (original space kept) | Its own normal position | Context for absolute children, minor nudges |
absolute | No | Nearest positioned ancestor | Overlays, badges, dropdowns, tooltips |
fixed | No | Viewport | Fixed navbar, floating buttons, modals |
sticky | Yes (then sticks) | Scroll container | Sticky table headers, sidebars |
3. Real World Example
Examples of position in real websites:
- Fixed navbar — stays at the top as you scroll (
position: fixed; top: 0;) - Shopping cart badge — number badge on a cart icon, anchored to the top-right of the icon (
position: absoluteon badge,position: relativeon icon wrapper) - Dropdown menu — appears below and aligned to its trigger button (
position: absoluterelative to the button's container) - Cookie consent banner — fixed to the bottom of the screen (
position: fixed; bottom: 0;) - Sticky table of contents — stays visible while scrolling a long article (
position: sticky; top: 100px;)
4. Code Example
/* ===== POSITION EXAMPLES ===== */
/* Fixed navbar */
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 64px;
background: #1e293b;
z-index: 100;
display: flex;
align-items: center;
padding: 0 24px;
}
/* Push page content below fixed navbar */
body { padding-top: 64px; }
/* ---- Notification badge on icon ---- */
.icon-wrapper {
position: relative; /* creates context for badge */
display: inline-block;
}
.notification-badge {
position: absolute;
top: -4px;
right: -4px;
width: 18px;
height: 18px;
background: #ef4444;
color: white;
font-size: 0.625rem;
font-weight: 700;
border-radius: 50%;
display: grid;
place-items: center;
}
/* ---- Dropdown menu ---- */
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-menu {
position: absolute;
top: 100%; /* just below the button */
left: 0;
min-width: 180px;
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
z-index: 50;
display: none;
}
.dropdown:hover .dropdown-menu,
.dropdown:focus-within .dropdown-menu {
display: block;
}
/* ---- Modal overlay ---- */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
z-index: 200;
display: none;
place-items: center;
}
.modal-overlay.open { display: grid; }
.modal-box {
background: white;
border-radius: 12px;
padding: 32px;
max-width: 500px;
width: 90%;
position: relative; /* for close button */
}
.modal-close {
position: absolute;
top: 16px;
right: 16px;
cursor: pointer;
}
/* ---- Sticky sidebar ---- */
.page-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 32px;
align-items: start;
}
.toc-sidebar {
position: sticky;
top: 80px; /* below fixed navbar */
max-height: calc(100vh - 100px);
overflow-y: auto;
}
5. Code Breakdown
position: relativeon.icon-wrapper- This makes the icon wrapper the positioning context for the badge. Without it, the badge with
position: absolute; top: -4px; right: -4pxwould position itself relative to the next positioned ancestor — possibly the entire page. top: 100%on.dropdown-menu- 100% of the parent's height. This places the dropdown exactly at the bottom edge of its parent button — regardless of how tall the button is. Much better than a hard-coded pixel value.
inset: 0on.modal-overlay- Shorthand for
top: 0; right: 0; bottom: 0; left: 0;. Combined withposition: fixed, this makes the overlay cover the entire viewport precisely. max-height: calc(100vh - 100px)on sidebarcalc()lets you mix units. Here: 100% of viewport height minus 100px (for the fixed header). This prevents the sticky sidebar from being taller than the screen, andoverflow-y: autoadds a scrollbar if the TOC is long.z-index: 100,200,50- Using layered z-index values: navbar (100) is above content, modal (200) is above navbar, dropdown (50) is above cards but below navbar. Using increments of 50+ gives room to add layers between.
6. Common Mistakes
-
Using
position: absolutewithout a positioned parent. If no ancestor hasposition: relative/absolute/fixed/sticky, the element positions relative to the root — almost never what you want. Always addposition: relativeto the parent. -
Forgetting
body { padding-top: Xpx; }after a fixed navbar. Fixed elements are removed from flow — content scrolls under them. Add padding-top equal to the navbar height to prevent content from being hidden. -
Using
z-indexwithoutposition.z-indexonly works on elements with a position other thanstatic. Setposition: relativeat minimum. -
Sticky not working. Common causes: a parent has
overflow: hiddenoroverflow: auto(breaks sticky), the element doesn't have atop/bottomvalue set, or the parent height equals the element height. - Z-index arms race. When things aren't stacking right, beginners add higher and higher z-index values. Instead, understand stacking contexts and use structured z-index values.
7. Best Practices
- Use positioning only when needed — prefer Flexbox/Grid for layout. Position is best for overlays, tooltips, badges.
- Always pair
position: absolutewithposition: relativeon the parent. - Use structured z-index values — define layers in a comment or variable system: content=1, dropdowns=50, navbar=100, modals=200.
- Use
inset: 0instead of writing all four offset properties for full-cover overlays. - Test sticky carefully — check no ancestor has
overflowthat breaks it. - Add
padding-topto body when using a fixed navbar.
8. Practice Exercise
- Create a fixed navigation bar that stays at the top when you scroll. Add
padding-topto body. - Create an icon with a notification badge in the corner using
relative+absolute. - Create a card with an image — place a "Featured" badge in the top-left corner of the image using positioning.
- Create a simple dropdown — a button that shows a menu below it on hover using
position: absolute; top: 100%;. - Scroll down a long page and test
position: stickyon a section heading.
9. Assignment
Build a page that uses all five position values correctly:
- Fixed: A top navigation bar and a "Back to Top" button in the bottom-right corner
- Relative: Used on three card wrappers as positioning context
- Absolute: A "Sale" badge on one card and a heart icon in the corner of another
- Sticky: A sidebar table of contents that sticks below the navbar
- Static: All other page elements
- Ensure correct z-index layering so fixed nav appears above all other content
10. Interview Questions
Q1: What are the five CSS position values?
Answer: static (default, in normal flow), relative (in flow, but can be offset from its normal position and creates context for absolute children), absolute (removed from flow, positioned relative to nearest positioned ancestor), fixed (removed from flow, positioned relative to viewport, does not scroll), sticky (in flow until a scroll threshold, then sticks).
Q2: What is a "positioned ancestor" and why does it matter?
Answer: A positioned ancestor is any ancestor element with a position value other than static. Absolutely positioned elements position themselves relative to their nearest positioned ancestor. Without one, they position relative to the viewport. That is why we add position: relative to parent containers when we need to anchor child elements.
Q3: What is the difference between position: fixed and position: sticky?
Answer: Fixed is always relative to the viewport and never moves when scrolling. Sticky stays in the normal flow until the user scrolls past a threshold (set by top/bottom), at which point it "sticks" and behaves like fixed. Sticky also stops sticking when its parent scrolls off screen.
Q4: Why doesn't z-index work on my element?
Answer: z-index only has effect on positioned elements (those with a position value other than static). Add position: relative to the element if you want z-index to take effect.
Q5: How do you overlay an element on top of another?
Answer: Give the parent position: relative, then give the overlay element position: absolute; inset: 0; (or top: 0; left: 0; right: 0; bottom: 0;) along with an appropriate z-index. Set a background colour (or transparent) and appropriate dimensions.
11. Additional Resources
- MDN Web Docs — CSS position (search "MDN CSS position")
- CSS-Tricks — Absolute Positioning Inside Relative Positioning (search this title)
- MDN — Understanding z-index (search "MDN understanding z-index")
- MDN — position: sticky (search "MDN sticky positioning")
- Josh Comeau — What The Heck Is CSS Stacking Context (search this title)