Code of the Day
IntermediateLayout Systems

Positioning

CSS positioning removes elements from normal flow to place them at explicit coordinates — four values with distinct behavior that learners frequently confuse.

Web FoundationsIntermediate7 min read
By the end of this lesson you will be able to:
  • Describe the behavior of all five position values
  • Explain the role of the containing block for absolutely positioned elements
  • Use position relative on a parent to anchor an absolutely positioned child
  • Distinguish between position fixed and position sticky
  • Avoid the common mistake of position absolute escaping to an unexpected ancestor

Flexbox and Grid arrange groups of related items. Sometimes you need something different: a modal overlay that covers the entire screen, a tooltip that floats above its trigger, a header that stays fixed at the top while the page scrolls. For those situations, CSS positioning removes an element from the normal flow arrangement entirely and lets you place it with explicit coordinates.

position: static (the default)

Every element starts with position: static. It participates in normal flow; the top, right, bottom, and left properties have no effect. You will rarely write this explicitly — it is the value you are canceling when you set anything else.

position: relative

A relatively positioned element stays in normal flow — it occupies its usual place and other elements lay out around it. But you can nudge it from that natural position using top, right, bottom, and left:

.nudged {
  position: relative;
  top: 8px;    /* moves down 8px from its natural position */
  left: 4px;   /* moves right 4px from its natural position */
}

The nudge is purely visual — the space the element would have occupied remains reserved in the flow, as if it had not moved. Other elements do not reflow.

More importantly: position: relative creates a positioned ancestor. Any absolutely positioned descendants will anchor to this element rather than continuing to search for one higher up the tree.

position: absolute

An absolutely positioned element is removed from normal flow entirely. Other elements ignore it — they lay out as if it does not exist. The element is then placed relative to the nearest ancestor with any position value other than static, called the .

.card {
  position: relative;   /* anchors the absolutely positioned badge */
}

.card .badge {
  position: absolute;
  top: 8px;
  right: 8px;           /* 8px from the card's top-right corner */
}

If no positioned ancestor exists, the element falls back to the initial containing block — roughly the viewport for most purposes — which is almost never what you intended. This is the most common source of "my element is in a completely wrong place" confusion.

Always ask: "What is the nearest positioned ancestor of this element?" before writing position: absolute. If you do not control what that ancestor is, add position: relative to the intended parent explicitly. Assuming the browser will pick the right ancestor without help is how elements end up somewhere unexpected.

position: fixed

A fixed element is removed from flow and positioned relative to the viewport — not any ancestor element. It stays there as the page scrolls.

.sticky-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 100;
}

Fixed elements create a classic problem: they overlap page content. Remember to add padding-top or margin-top to the page body equal to the fixed header's height, so content is not hidden underneath it.

One subtlety: transform, filter, or will-change on any ancestor element can move a fixed element off the viewport anchor and onto that ancestor instead — the same stacking context mechanism you will meet in the next lesson.

position: sticky

Sticky positioning is a hybrid. The element behaves like position: relative until it reaches a specified scroll threshold, then it "sticks" like position: fixed within the boundary of its scroll container.

.section-heading {
  position: sticky;
  top: 0;    /* sticks when it reaches the top of the viewport */
}

The element stays sticky only while its parent is in view. Once the parent scrolls fully out of view, the sticky element scrolls away with it — it does not stick to the viewport indefinitely. This behavior is intentional and makes sticky perfect for section headers in long scrollable lists.

/* A practical table header that stays visible while scrolling the table body */
table thead th {
  position: sticky;
  top: 0;
  background: white;  /* prevent content bleeding through on scroll */
}

Sticky positioning requires a scroll container with a defined height, and the sticky element must be a direct child of that container (or a descendant within it). If sticky is not working, check that no ancestor between the sticky element and the scroll container has overflow: hidden or overflow: auto — that collapses the scroll context and breaks the sticky behavior.

Choosing a position value

A quick guide to the decision:

NeedValue
Default, participates in flowstatic
Nudge an element visually without affecting flowrelative
Anchor for absolute childrenrelative
Overlay / badge / tooltip over a specific parentabsolute
Fixed header / footer visible at all timesfixed
Table headers, sticky section titlessticky

Where to go next

Positioning introduces the concept of elements layering on top of one another. Once you have layers, you need a way to control which one draws on top. Next: z-index and Stacking Contexts — why z-index: 9999 sometimes does nothing.

Finished reading? Mark it complete to track your progress.

On this page