Written by Rahul · Frontend Engineer at Google · Updated 2025
CSS positioning is one of those things that seems simple until you actually need to position something. I've been writing CSS for 8+ years and I still occasionally have to think about which position value to use. Here's the definitive guide.
All 5 Position Values
1. position: static (Default)
Every element is static by default. It flows in normal document order. top, right, bottom, left, and z-index have NO effect.
/* This does absolutely nothing */
.element {
position: static;
top: 50px; /* Ignored! */
z-index: 999; /* Ignored! */
}2. position: relative
The element stays in normal flow but can be offset from its original position. The space it originally occupied is preserved.
.badge {
position: relative;
top: -10px; /* Moves UP 10px from original position */
left: 5px; /* Moves RIGHT 5px from original position */
}
/* Common use: creating a positioning context for absolute children */
.card {
position: relative; /* This is the key! */
}
.card .badge {
position: absolute;
top: -8px;
right: -8px;
}3. position: absolute
The element is removed from normal flow. It positions relative to its nearest positioned ancestor (an ancestor with position other than static). If none exists, it uses the viewport.
/* Notification badge on an icon */
.icon-wrapper {
position: relative; /* Creates positioning context */
display: inline-block;
}
.notification-dot {
position: absolute;
top: -4px;
right: -4px;
width: 8px;
height: 8px;
background: red;
border-radius: 50%;
}
/* Modal overlay */
.modal-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
/* Or: inset: 0; (shorthand) */
background: rgba(0, 0, 0, 0.5);
}4. position: fixed
Like absolute, but positions relative to the viewport. It doesn't move when the page scrolls.
/* Sticky header */
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Don't forget to add padding to body! */
body {
padding-top: 64px; /* Height of navbar */
}
/* Back to top button */
.back-to-top {
position: fixed;
bottom: 20px;
right: 20px;
}
/* Cookie banner */
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
}5. position: sticky
A hybrid of relative and fixed. It's relative until a scroll threshold is met, then becomes fixed. You must specify at least one of top, right, bottom, or left.
/* Sticky table headers */
.table thead th {
position: sticky;
top: 0;
background: white;
z-index: 10;
}
/* Sticky sidebar */
.sidebar {
position: sticky;
top: 80px; /* Below fixed navbar */
height: fit-content;
}
/* Section headers that stick during scroll */
.section-header {
position: sticky;
top: 0;
background: var(--background);
padding: 12px 0;
border-bottom: 1px solid var(--border);
}Common Gotchas in Production
Gotcha 1: Sticky Not Working
/* ❌ Sticky won't work if any ancestor has overflow: hidden */
.parent {
overflow: hidden; /* This kills sticky! */
}
.child {
position: sticky; /* Won't stick */
top: 0;
}
/* ❌ Also won't work without a threshold */
.element {
position: sticky; /* Missing top/bottom/left/right! */
}
/* ✅ Fix: Remove overflow:hidden from ancestors OR restructure HTML */Gotcha 2: Fixed Inside Transform
/* ❌ Fixed element inside a transformed parent doesn't work as expected */
.parent {
transform: translateX(0); /* Creates new containing block! */
}
.child {
position: fixed; /* Now fixed relative to .parent, not viewport! */
top: 0;
}
/* This also applies to: filter, perspective, will-change, contain */Gotcha 3: Z-Index Stacking Context
Best Practices
- Use
stickyfor headers/sidebars — it's cleaner than scroll event listeners - Always set
position: relativeon parent when usingabsoluteon children - Use
inset: 0shorthand instead oftop: 0; right: 0; bottom: 0; left: 0 - Add body padding when using fixed headers to prevent content overlap
- Avoid fixed positioning on mobile — it behaves poorly with virtual keyboards