Positioning

Take elements out of normal flow and place them precisely using the position property and its top/right/bottom/left offsets, and understand what containing block each position value uses.

The position property is how you override normal document flow to place an element exactly where you want it — anchored to the viewport, layered on top of other content, or stuck in place while scrolling. Each of the five position values behaves according to a different containing block, which is the key to predicting exactly where a positioned element will land.

static, relative, and absolute

position: static is the default for every element — it simply sits in normal flow, and top/right/bottom/left have no effect on it whatsoever.

position: relative keeps the element in normal flow (it still takes up its original space, and other elements are laid out as if it hadn't moved) but then lets you nudge it visually using top/right/bottom/left, offset from where it would otherwise have sat. Its containing block for percentage-based sizing stays the same as static — but importantly, position: relative ALSO establishes a new positioning context for any absolutely positioned descendants, which is its single most common practical use.

position: absolute completely removes the element from normal flow — it takes up no space at all where it would have been, and other elements act as if it doesn't exist. It is then positioned using top/right/bottom/left relative to its nearest ANCESTOR that has a position value other than static (commonly a relative or absolute ancestor). If no ancestor is positioned, it falls back to being positioned relative to the initial containing block, which is effectively the whole page/viewport.

• static — default; top/right/bottom/left ignored • relative — stays in flow, but can be nudged with offsets from its normal position; establishes a positioning context for absolute children • absolute — removed from flow entirely; positioned relative to nearest positioned ancestor (or the viewport if none exists)

fixed and sticky

position: fixed removes the element from normal flow, just like absolute, but anchors it to the VIEWPORT itself rather than any ancestor — it stays glued in the same place on screen even as the page scrolls. This is the standard technique behind persistent headers, chat widgets, and 'back to top' buttons.

position: sticky is a hybrid: the element behaves like position: relative (stays in normal flow, in its natural position) until the page scrolls to the point where it would go offscreen, at which point it 'sticks' and behaves like position: fixed within the bounds of its containing block, until that container scrolls fully out of view, at which point it unsticks and scrolls away with the rest of the content. A sticky element requires at least one of top/right/bottom/left to be set, or it will behave exactly like relative and never actually stick.

Offsets and the inset shorthand

top, right, bottom, and left set how far the element's edge should be from the corresponding edge of its containing block, and they only take effect on elements with a position value other than static. inset is a shorthand that can set all four in one declaration, following the same 1-4 value pattern as margin/padding (inset: 10px; sets all four; inset: 10px 20px; sets top/bottom then left/right).

Positioned elements (anything other than static) also participate in stacking — they render on top of static elements by default, and among positioned elements, later-painted or higher z-index elements appear above earlier ones. Full control over this stacking order using z-index and stacking contexts is covered in a later, dedicated lesson.

Positioning Properties

.relative-parent {
  position: relative;
}

.absolute-child {
  position: absolute;
  top: 8px;
  right: 8px;
}

.sticky-header {
  position: sticky;
  top: 0;
}

.fixed-button {
  position: fixed;
  inset: auto 16px 16px auto; /* bottom-right corner of viewport */
}

Code Examples

.card establishes a positioning context by declaring position: relative (even with no offsets of its own). Its .badge child then positions absolutely relative to .card's edges rather than the whole page, letting it hang slightly outside the card's top-right corner — a classic notification-badge pattern.

As a long table scrolls inside its container, the header row behaves normally (in-flow) until it would scroll out of view at the top, at which point it sticks to top: 0 and stays visible, only releasing once the entire <table> has scrolled past.

Because fixed positioning anchors to the viewport rather than any ancestor, this button stays in the exact same visual spot on screen — bottom-right corner — no matter how far the user scrolls down the page.

.card {
  position: relative;
}

.card .badge {
  position: absolute;
  top: -8px;
  right: -8px;
  background: crimson;
  color: white;
  border-radius: 50%;
  padding: 4px 8px;
}
thead th {
  position: sticky;
  top: 0;
  background: white;
  z-index: 1;
}
.scroll-top-btn {
  position: fixed;
  bottom: 24px;
  right: 24px;
}

Positioning Hierarchy

Each step further removes an element from — or changes how it relates to — normal document flow, and each uses a different containing block for its offsets.

Normal Flow
    |
relative   (nudged, but keeps its original space; anchors absolute children)
    |
absolute   (removed from flow; anchored to nearest positioned ancestor)
    |
fixed / sticky   (anchored to the viewport, or hybrid in-flow + fixed)

Important Rules

top/right/bottom/left only affect elements whose position is NOT static.

position: relative keeps its original space in the layout; position: absolute and fixed do not.

An absolutely positioned element looks up its ancestor chain for the nearest ancestor with a non-static position to use as its containing block; if none exists, it uses the viewport.

position: sticky needs at least one offset (commonly top) declared, or it behaves exactly like position: relative and never sticks.

Why this matters on real projects

The 'relative parent, absolute child' pattern is one of the most-used techniques in real interfaces — tooltips, dropdown menus, notification badges, and modal close buttons almost always rely on it. Sticky positioning has largely replaced JavaScript scroll-listener hacks for sticky headers and table headers, since it's implemented natively by the browser and performs far better than recalculating position on every scroll event..badge { position: absolute; top: 0; right: 0; } /* parent.card has no position set */.card { position: relative; }.badge { position: absolute; top: 0; right: 0; }

Without position: relative (or any non-static value) on .card, the .badge's absolute positioning skips right past it and anchors to the entire page/viewport instead — landing the badge in the corner of the whole browser window rather than the corner of the card.

[Choose the Correct Syntax] You want a <div class="popup"> to be positioned relative to its parent <div class="card">, not the whole page. Which pair of rules achieves this correctly? A. .card { position: static; } .popup { position: absolute; top: 0; } B. .card { position: relative; } .popup { position: absolute; top: 0; } C. .card { } .popup { position: relative; top: 0; } D. .card { position: fixed; } .popup { position: static; top: 0; }

B. .card { position: relative; } .popup { position: absolute; top: 0; } — An absolutely positioned element anchors to its nearest ANCESTOR with a non-static position. Giving .card position: relative (even with no offsets) makes it the containing block for .popup's position: absolute, so the popup positions relative to the card rather than escaping to the whole viewport.

Create a tooltip that is hidden by default and, when shown, appears positioned absolutely 100% above its trigger button, anchored correctly to that specific button rather than the whole page..btn-wrapper { position: relative; }.tooltip { position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); }

Give the button's wrapping container position: relative so the tooltip's position: absolute anchors to it instead of the viewport.