CSSMedium

How does CSS positioning work?

01

The Short Answer

CSS position controls how an element is placed in the document flow and what it's positioned relative to. The five values — static, relative, absolute, fixed, and sticky — each create different positioning contexts. Understanding them is essential for building layouts, overlays, tooltips, sticky headers, and any UI that breaks out of the normal document flow.

02

static (Default)

position: static is the default — elements flow normally in the document. They stack vertically (block elements) or horizontally (inline elements) according to the normal flow. The top, right, bottom, left, and z-index properties have no effect on static elements.

static.csscss
.element {
  position: static; /* Default — no need to declare */
  top: 20px;        /* ❌ Ignored — has no effect on static elements */
  z-index: 10;      /* ❌ Ignored — static elements don't create stacking context */
}
03

relative

position: relative keeps the element in the normal flow (it still takes up its original space) but lets you offset it from its normal position using top, right, bottom, left. The offset is visual only — surrounding elements don't move. Its main use is as a positioning context for absolutely-positioned children.

relative.csscss
.badge-container {
  position: relative; /* Creates positioning context for children */
}

.badge {
  position: absolute; /* Positioned relative to .badge-container */
  top: -8px;
  right: -8px;
}

.nudged {
  position: relative;
  top: -2px;   /* Moves up 2px visually */
  left: 4px;   /* Moves right 4px visually */
  /* Original space is still reserved — no layout shift */
}

The most common use of relative isn't to offset the element itself — it's to establish a containing block for absolute children. Without a positioned ancestor, absolute elements position relative to the viewport.

04

absolute

position: absolute removes the element from the normal flow entirely — it no longer takes up space, and other elements act as if it doesn't exist. It positions itself relative to its nearest positioned ancestor (any ancestor with position other than static). If no positioned ancestor exists, it positions relative to the initial containing block (the viewport).

absolute.csscss
/* Dropdown menu positioned below its trigger */
.dropdown-wrapper {
  position: relative; /* Containing block for the menu */
}

.dropdown-menu {
  position: absolute;
  top: 100%;     /* Below the wrapper */
  left: 0;
  width: 200px;
  z-index: 50;   /* Above other content */
}

/* Overlay covering the entire viewport */
.modal-overlay {
  position: absolute;
  inset: 0;      /* top: 0; right: 0; bottom: 0; left: 0 */
  background: rgba(0, 0, 0, 0.5);
}

/* Centering with absolute positioning */
.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

The inset shorthand

inset: 0 is shorthand for top: 0; right: 0; bottom: 0; left: 0. Combined with position: absolute, it makes an element fill its positioned parent completely — useful for overlays and backgrounds.

05

fixed

position: fixed is like absolute but positions relative to the viewport instead of a positioned ancestor. The element stays in the same spot on screen even when the user scrolls. It's removed from the normal flow and doesn't affect other elements' layout.

fixed.csscss
/* Fixed header — stays at top during scroll */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 64px;
  z-index: 100;
  background: white;
}

/* Need padding on body to prevent content hiding behind fixed header */
body {
  padding-top: 64px;
}

/* Floating action button — bottom right corner */
.fab {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 50;
}

/* Cookie banner — fixed to bottom */
.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
}

A gotcha: fixed positioning breaks when an ancestor has transform, filter, or will-change set — the element positions relative to that ancestor instead of the viewport. This is a common source of bugs with modals and tooltips inside transformed containers.

06

sticky

position: sticky is a hybrid — the element behaves like relative until it reaches a scroll threshold (defined by top, bottom, etc.), then it "sticks" and behaves like fixed within its containing block. When the containing block scrolls out of view, the sticky element goes with it.

sticky.csscss
/* Sticky section headers — stick when scrolled to top */
.section-header {
  position: sticky;
  top: 0;           /* Sticks when it reaches the top of viewport */
  background: white;
  z-index: 10;
  /* Scrolls normally until top: 0 is reached, then sticks */
  /* Unsticks when its parent container scrolls out of view */
}

/* Sticky sidebar — stays visible while scrolling main content */
.sidebar {
  position: sticky;
  top: 80px;        /* Sticks 80px from top (below fixed header) */
  align-self: start; /* Important in flex/grid layouts */
}

Sticky requires a top, bottom, left, or right value to know when to stick. It also requires the parent to have enough height for scrolling — if the parent is exactly the height of the sticky element, there's nowhere to scroll and it won't stick. In flex/grid layouts, add align-self: start to prevent the sticky element from stretching.

07

Full Comparison

ValueIn flow?Positioned relative toScrolls with page?
staticYesN/A (normal flow)Yes
relativeYes (keeps space)Its own normal positionYes
absoluteNo (removed)Nearest positioned ancestorYes (with ancestor)
fixedNo (removed)ViewportNo (stays in place)
stickyYes (until stuck)Viewport (when stuck)Partially (sticks within parent)
08

Common Patterns

  • Tooltips / Dropdowns
    • Parent: relative, Child: absolute + top/left offsets
  • Modals / Overlays
    • Fixed to viewport with inset: 0 and z-index
  • Sticky headers
    • position: sticky + top: 0 on the header element
  • Badge on avatar
    • Avatar wrapper: relative, Badge: absolute + top-right offset
  • Centering
    • Absolute + top: 50% + left: 50% + transform: translate(-50%, -50%)
09

Why Interviewers Ask This

CSS positioning is fundamental to layout — every tooltip, modal, dropdown, sticky header, and overlay uses it. Interviewers ask this to confirm you understand the document flow, know what each position value does, can explain containing blocks (what absolute positions relative to), and know the gotchas (fixed breaking inside transforms, sticky needing a threshold value). It's a practical question that comes up in every frontend project.

Quick Revision Cheat Sheet

static: Default, normal flow, top/left/z-index ignored

relative: In flow + offset from self, creates containing block for children

absolute: Out of flow, positions to nearest positioned ancestor

fixed: Out of flow, positions to viewport, unaffected by scroll

sticky: In flow until threshold, then sticks within parent

Gotcha: transform/filter on ancestor breaks fixed positioning