The position property controls how an element is placed on the page and how it interacts with surrounding elements. Understanding it requires understanding the concept of containing block and document flow.

Key concept: When you use top, right, bottom, or left — they only work if position is set to something other than static.

position: static (Default)

static is the default value for every element. Static elements follow the normal document flow — they appear in the order they are written in the HTML, left to right, top to bottom.

The top, right, bottom, and left properties have no effect on static elements. You almost never write position: static explicitly — it is just the default.

/* This does nothing — static is the default */
.box {
  position: static;
  top: 20px;     /* has no effect */
  left: 50px;    /* has no effect */
}

position: relative

relative positions an element relative to its own normal position in the document flow. The element still occupies its original space — other elements are not affected.

The most important use of relative is as a positioning context for absolutely positioned children.

.box {
  position: relative;
  top: 20px;     /* move DOWN 20px from its normal position */
  left: 30px;    /* move RIGHT 30px from its normal position */
}

/* Common pattern: parent is relative so child absolute works */
.parent {
  position: relative;  /* creates positioning context */
}
.child {
  position: absolute;
  top: 0;
  right: 0;            /* anchors to top-right of parent */
}

Think of position: relative as "I keep my spot, but I can shift from it." It is most valuable as a container for absolutely positioned children.

position: absolute

absolute removes the element from the normal document flow completely — other elements act as if it does not exist. The element is then positioned relative to its nearest positioned ancestor (any parent with position set to anything other than static).

If no positioned ancestor exists, the element positions itself relative to the <html> element (the initial containing block).

/* Tooltip that appears on top-right of its parent */
.card {
  position: relative;     /* <- IMPORTANT: creates the anchor */
  padding: 1.5rem;
}

.badge {
  position: absolute;
  top: 10px;
  right: 10px;
  background: red;
  color: white;
  border-radius: 999px;
  padding: 2px 8px;
  font-size: 12px;
}

/* Common: centered overlay */
.overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;  /* fill parent completely */
  background: rgba(0, 0, 0, 0.5);
}

Always set position: relative on the parent element when using position: absolute on a child. Otherwise the child will escape and position itself relative to the page.

position: fixed

fixed removes the element from normal flow and positions it relative to the browser viewport — the visible window. It stays in place even when the user scrolls.

/* Sticky navigation bar that stays at the top */
.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  background: #fff;
  z-index: 100;      /* sit above other content */
  box-shadow: 0 2px 8px rgba(0,0,0,.1);
}

/* Always-visible back-to-top button */
.back-to-top {
  position: fixed;
  bottom: 2rem;
  right: 2rem;
  background: #6d28d9;
  color: #fff;
  border-radius: 50%;
  width: 44px;
  height: 44px;
}

/* IMPORTANT: add top padding to body to prevent content hiding behind fixed nav */
body {
  padding-top: 60px;
}

Fixed elements take up no space in the document, so they can hide content beneath them. Always add padding or margin to prevent overlap.

position: sticky

sticky is a hybrid between relative and fixed. The element behaves like relative until the user scrolls to a certain point, then it "sticks" and behaves like fixed within its parent container.

/* Sticky table header */
thead th {
  position: sticky;
  top: 0;              /* sticks to top when scrolling */
  background: #fff;    /* IMPORTANT: needs background or content shows through */
  z-index: 1;
}

/* Sidebar that sticks while scrolling */
.sidebar {
  position: sticky;
  top: 80px;           /* sticks 80px from top of viewport */
  height: fit-content;
}

/* Sticky section headings (alphabet list style) */
.section-label {
  position: sticky;
  top: 0;
  background: #f8f7ff;
  padding: 0.5rem 1rem;
  font-weight: 700;
}

position: sticky not working? Check these common causes: (1) The parent has overflow: hidden set — remove it. (2) You forgot to set top, left, right, or bottom. (3) The parent is too short to allow scrolling.

Quick Comparison Table

ValueRemoved from Flow?Positioned Relative ToScrolls With Page?
staticNoNormal flowYes
relativeNo (keeps its space)Its own original positionYes
absoluteYesNearest positioned ancestorYes (unless inside fixed)
fixedYesBrowser viewportNo — always visible
stickyNo (until threshold)Scroll position + parentPartially

Common Patterns

  • Notification badge on icon — parent relative, badge absolute top-right
  • Image caption overlay — container relative, caption absolute bottom-0
  • Sticky nav barfixed or sticky at top: 0
  • Modal / dialog — backdrop fixed covering viewport, dialog fixed centered with transform
  • Sticky sidebar — sidebar sticky with top offset
  • Tooltip — tooltip absolute, positioned relative to relative parent