CSS positioning controls how an element participates in normal document flow and how it can be offset. It is used for tooltips, badges, sticky headers, fixed navigation, modals, floating action buttons, and layered UI pieces.
The most important idea is positioning context. An absolutely positioned element does not simply position itself against the page every time. It looks for the nearest ancestor whose position is not static. If that ancestor is missing, the element may jump to an unexpected place.
The position property controls how an element is placed in the document. Once positioned, you use top, right, bottom, and left to offset it.
static elements stay in normal flow and ignore offsets. relative elements stay in flow but can be visually nudged. absolute and fixed elements are removed from normal flow. sticky behaves like relative until a scroll threshold is reached, then it behaves like fixed inside its scroll area.
| Value | Description | Offset relative to |
|---|---|---|
| static | Default - normal document flow, offsets ignored | N/A |
| relative | Offset from its normal position, still occupies space | Its own normal position |
| absolute | Removed from flow, positioned relative to nearest positioned ancestor | Nearest positioned ancestor |
| fixed | Removed from flow, stays fixed on screen during scroll | Viewport |
| sticky | Relative until scroll threshold, then fixed | Scroll container |
/* static - default, no positioning */
.static { position: static; }
/* relative - offset from normal position, still takes up space */
.relative {
position: relative;
top: 20px; /* moves DOWN 20px from normal position */
left: 30px; /* moves RIGHT 30px */
/* Original space is preserved */
}
/* absolute - removed from flow, positioned inside nearest positioned ancestor */
.container {
position: relative; /* establishes positioning context */
width: 400px;
height: 300px;
}
.absolute {
position: absolute;
top: 0;
right: 0; /* top-right corner of .container */
width: 100px;
height: 100px;
}
/* Center absolutely positioned element */
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* fixed - stays in viewport during scroll */
.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);
}
.back-to-top {
position: fixed;
bottom: 30px;
right: 30px;
}
/* sticky - relative until scroll threshold, then fixed */
.sticky-header {
position: sticky;
top: 0; /* sticks when it reaches top of viewport */
background: white;
z-index: 100;
}
/* z-index - stacking order (higher = on top) */
.modal-overlay { z-index: 999; }
.modal { z-index: 1000; }
.tooltip { z-index: 1001; }
A tooltip is a classic example of position: absolute. The wrapper becomes the positioning context with position: relative, and the tooltip is placed in relation to that wrapper instead of the entire page.
.tooltip-wrapper {
position: relative;
display: inline-block;
}
.tooltip-text {
position: absolute;
bottom: 125%; /* above the element */
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 6px 10px;
border-radius: 4px;
white-space: nowrap;
font-size: 0.85rem;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s;
z-index: 10;
}
/* Arrow pointing down */
.tooltip-text::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: #333;
}
.tooltip-wrapper:hover .tooltip-text {
opacity: 1;
visibility: visible;
}
<span class="tooltip-wrapper">
Hover me
<span class="tooltip-text">This is a tooltip!</span>
</span>
The position property controls how an element participates in layout and how offset properties such as top, right, bottom, and left behave. static is the default and follows normal document flow. relative keeps the element in flow but visually offsets it. absolute removes it from normal flow and positions it against the nearest positioned ancestor.
fixed positions an element relative to the viewport, which is useful for sticky toolbars or floating actions but can cover content if spacing is not planned. sticky behaves like relative until a scroll threshold is reached, then acts fixed within its scrolling container. Sticky requires a scrollable context and an offset such as top.
For absolute positioning, the parent usually needs position: relative. Without a positioned ancestor, the element may position against a higher-level containing block and appear in surprising places. Always define the relationship between the positioned child and the container that owns it.
z-index only works on positioned elements and elements that participate in certain layout contexts. If z-index appears to be ignored, the issue is often a stacking context created by properties such as position with z-index, transform, opacity below 1, filter, isolation, or fixed/sticky positioning.
.site-header {
position: sticky;
top: 0;
z-index: 100;
}
.dropdown {
position: absolute;
z-index: 200;
}
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 900;
}
.modal {
position: fixed;
z-index: 1000;
}
z-index works only within stacking contexts, and many properties create new stacking contexts, including position with z-index, opacity less than 1, transform, filter, isolation, and certain containment rules. A high z-index inside one context may still appear below a lower value in another context.
Positioning should not replace layout systems. Use flexbox or grid for ordinary page structure, then use positioning for overlays, badges, tooltips, popovers, and anchored UI. Test at multiple viewport sizes, zoom levels, and writing directions. Fixed elements need safe-area and keyboard considerations on mobile.
Accessible positioned UI needs focus handling, escape behavior, readable source order, and proper semantics. Tooltips should not hide essential content. Dropdowns and modals need keyboard navigation and focus management. Visual placement and interaction behavior must work together.
The card owns the badge position because it is the nearest positioned ancestor.
.card {
position: relative;
padding: 1rem;
border: 1px solid #d1d5db;
border-radius: .75rem;
}
.card__badge {
position: absolute;
top: .75rem;
right: .75rem;
padding: .25rem .5rem;
background: #2563eb;
color: white;
border-radius: 999px;
}
Sticky works inside its scrolling context with an offset.
.lesson-list {
max-height: 70vh;
overflow: auto;
}
.lesson-heading {
position: sticky;
top: 0;
z-index: 2;
background: white;
padding: .75rem 1rem;
border-bottom: 1px solid #e5e7eb;
}
Positioning a badge absolute without setting position: relative on the card.
Set the card to position: relative so the badge uses the card as its context.
Increasing z-index forever when an element still appears behind another layer.
Inspect parent stacking contexts and move the element or adjust the parent layer.
Using fixed for a header that should stop inside a section.
Use sticky on the section header when it should stick only while that scroll area is active.
It probably cannot find a positioned ancestor, so it is using a higher-level containing block. Add position: relative to the intended parent.
Check that a top, right, bottom, or left value is set and that no parent scroll/overflow setup prevents the sticky behavior.
For normal block elements, z-index usually needs a positioned element. Grid and flex items have some special behavior, but positioning is the common case.
Use it when absolute positioning starts an element at 50% and you need to pull the element back by half of its own width and height to truly center it.
Explore 500+ free tutorials across 20+ languages and frameworks.