CSS Positioning

This section documents general HTML5 and CSS concepts — it is not tied to any specific framework or library. This content was generated with the assistance of AI. Verify it against current MDN documentation and browser-support tables (caniuse.com) before relying on it in production, since HTML/CSS features and browser support continue to evolve.

The CSS position property controls how an element is placed on the page: whether it stays in the normal flow of the document alongside its siblings, or is taken out of that flow and placed relative to some other reference point. There are five possible values — static, relative, fixed, absolute, and sticky — and each one answers two questions differently: does the element still take up space in the document flow that surrounding elements have to respect, and what is the element’s position measured relative to. Once an element has any position value other than static, the top, right, bottom, and left properties (collectively known as the "offset properties") become active, and the element also becomes eligible to participate in z-index stacking, covered at the end of this page.

static

static is the default value of position for every element. Elements with position: static are laid out according to the normal flow of the document — block elements stack vertically, inline elements flow horizontally — exactly as if position had never been mentioned at all. The offset properties (top, right, bottom, left) have no effect on a statically positioned element.

.box {
  position: static; /* the default; rarely written explicitly */
}

relative

position: relative keeps the element in the normal document flow — it still occupies its original space, and sibling elements are laid out exactly as if the element had not moved — but it lets the offset properties nudge the element visually away from that original position.

.box {
  position: relative;
  top: 20px;   /* pushes the box 20px down from where it would normally sit */
  left: 10px;  /* pushes the box 10px right from where it would normally sit */
}

The key detail is that the space the element would have occupied is preserved in the layout — only the element’s own rendered position is shifted. This can leave a visible gap where the element used to be, and can make the shifted element overlap neighboring content.

fixed

position: fixed positions the element relative to the viewport (the browser window) instead of any ancestor element. A fixed element stays glued to the same point on screen even as the page is scrolled, which makes it the standard choice for elements such as sticky headers, "back to top" buttons, or persistent notification bars.

.banner {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
}

A fixed element is removed from the normal document flow: the space it would have occupied collapses, and surrounding elements lay out as if the fixed element were not there at all.

absolute

position: absolute positions the element relative to its nearest positioned ancestor — that is, the closest ancestor whose own position is anything other than static. If no ancestor qualifies, the element is positioned relative to the initial containing block, which in practice is usually the <html> element.

.tooltip-container {
  position: relative; /* establishes the positioning context for the child below */
}

.tooltip {
  position: absolute;
  top: 100%;
  left: 0;
}

Like fixed, an absolutely positioned element is removed from the normal document flow: it no longer reserves any space, and it can overlap other elements as if it were floating in its own layer above the rest of the page. This is why position: absolute is almost always paired with position: relative on a parent element — otherwise the child ends up positioned against the whole document rather than the intended container.

sticky

position: sticky is a hybrid of relative and fixed. The element behaves like position: relative — staying in the normal flow — until the page is scrolled past a threshold defined by an offset property (most commonly top), at which point it "sticks" and behaves like position: fixed, but only within the bounds of its containing block rather than the whole viewport. Once the scroll position moves past the bottom of that containing block, the element scrolls away along with it.

.section-heading {
  position: sticky;
  top: 0; /* sticks to the top of the viewport once its container reaches this point */
}

Sticky positioning is commonly used for table headers that stay visible while the body scrolls, or for section headings in a long list that pin to the top while their section is on screen. Unlike fixed, a sticky element does not collapse the space it occupies in the flow — it behaves exactly like a relative element until the scroll threshold is crossed.

Comparing the Five Values

Value Positioned relative to Stays in normal flow? Reserves its original space?

static

Normal document flow (no positioning context)

Yes

Yes

relative

Its own original position

Yes

Yes

fixed

The viewport

No

No

absolute

Nearest positioned ancestor (or the initial containing block)

No

No

sticky

Its own original position, then the containing block once a scroll threshold is reached

Yes, until the threshold; behaves as fixed afterward

Yes

The diagram below contrasts what happens to two neighboring boxes (Box A and Box C) around a box that is given each position value (Box B). static and relative keep the three boxes flowing one after another — relative only shifts Box B’s own rendering, leaving a gap and reserved space behind. fixed and absolute remove Box B from the flow entirely, so Box A and Box C collapse together as neighbors, while Box B is rendered independently, floating on top of the page. sticky behaves like relative until scrolling crosses its threshold, after which Box B pins in place while Box A and Box C continue to scroll normally.

flowchart TB subgraph static["position: static (default)"] direction LR S1[Box A] --> S2[Box B] --> S3[Box C] end subgraph relative["position: relative"] direction LR R1[Box A] --> R2[Box B - reserved space] --> R3[Box C] R2 -.top/left offset.-> R2b[Box B rendered here instead] end subgraph fixed["position: fixed"] direction LR F1[Box A] --> F3[Box C - collapses next to Box A] F2[Box B - pinned to viewport, out of flow] end subgraph absolute["position: absolute"] direction LR AB1[Box A] --> AB3[Box C - collapses next to Box A] AB2[Box B - positioned against ancestor, out of flow] end subgraph sticky["position: sticky"] direction LR subgraph beforeScroll["before scroll threshold"] direction LR K1[Box A] --> K2[Box B] --> K3[Box C] end subgraph afterScroll["after scroll threshold"] direction LR K4[Box A - scrolled away] --> K5[Box B - pinned to container edge] --> K6[Box C] end end

Stacking Order with z-index

The z-index property only has an effect once an element’s position is set to anything other than static — it has no effect on statically positioned elements. z-index governs the stacking order: which positioned elements render in front of, or behind, one another when they overlap.

.modal {
  position: fixed;
  z-index: 100; /* renders above elements with a lower z-index */
}

.modal-backdrop {
  position: fixed;
  z-index: 90;
}

Its value is an integer, which can be positive (rendered above elements with a lower value) or negative (rendered below them), with 0 commonly used as the baseline. Elements with a higher z-index are painted on top of elements with a lower one, regardless of their order in the HTML source. Positioned elements without an explicit z-index stack according to source order, effectively acting as if their z-index were auto.

z-index is essential for controlling overlapping UI — modal dialogs and their backdrops, dropdown menus, tooltips, and sticky headers all rely on it to guarantee they render above (or below) the rest of the page, which is particularly important once animations or transitions cause elements to move and overlap.