The CSS position property controls how an element is placed in the document and what it positions itself relative to. It is the foundation of overlays, dropdowns, sticky navigation, tooltips, and any UI pattern where elements need to sit on top of or independently from the normal page flow. This guide covers all five position values with practical examples and the rules that govern each one.

How CSS position works

By default, every element sits in the normal document flow, stacked one after another based on the order it appears in the HTML. The position property lets you change this behaviour in different ways depending on the value you choose.

Once you give an element a position value other than static, you can use the offset properties top, right, bottom, and left to move it. These offsets mean different things for different position values, which is the part most developers need to understand clearly.

Quick reference

ValueIn document flow?Positions relative toCommon use
staticYesN/A (offsets ignored)Default, reset
relativeYesIts own normal positionContaining block, minor nudging
absoluteNoNearest positioned ancestorTooltips, badges, overlays
fixedNoBrowser viewportSticky nav, modals, chat buttons
stickyYesScroll container + own positionSticky headers, sidebar sections

position: static

static is the default value. Every element has it unless you explicitly change it. A statically positioned element sits exactly where the normal document flow places it. The top, right, bottom, left, and z-index properties have no effect on static elements.

/* Default — no need to write this explicitly */
.element {
  position: static;
}

/* Common use: resetting position on a child inside a positioned parent */
.reset-position {
  position: static;
}

position: relative

relative keeps the element in the normal document flow. Its original space is preserved, meaning surrounding elements do not reflow. The offset properties move it visually from where it would naturally sit, but the space it occupied stays in place.

.nudged {
  position: relative;
  top: 8px;   /* moves DOWN 8px from its natural position */
  left: 12px; /* moves RIGHT 12px from its natural position */
}

The most important use of position: relative is not moving an element — it is creating a containing block for absolutely positioned children. Set position: relative on a parent to make absolutely positioned descendants position themselves relative to that parent instead of the page.

.card {
  position: relative; /* becomes the containing block */
}

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

position: absolute

An absolutely positioned element is completely removed from the document flow. The surrounding elements reflow as if it does not exist. It is then placed relative to the nearest ancestor that has a position value other than static.

If no positioned ancestor exists, the element positions itself relative to the initial containing block, which is effectively the html element at the top of the page.

.parent {
  position: relative; /* required — makes .parent the containing block */
  width: 300px;
  height: 200px;
}

.child {
  position: absolute;
  top: 0;    /* 0px from parent's top edge */
  right: 0;  /* 0px from parent's right edge */
  width: 80px;
  height: 80px;
}

Centering with position absolute

A classic use case for absolute positioning is centering an element precisely inside a parent when flexbox or grid is not available or appropriate:

.parent {
  position: relative;
}

.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* translate(-50%, -50%) shifts the element back by half its own size */
}

Full-coverage overlay

.card {
  position: relative;
  overflow: hidden;
}

.card-overlay {
  position: absolute;
  inset: 0; /* shorthand for top: 0; right: 0; bottom: 0; left: 0 */
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.card:hover .card-overlay {
  opacity: 1;
}

Remember: absolute positioning removes the element from the flow. If a parent's height depends on its children's content, absolutely positioned children will not contribute to that height. The parent may collapse to zero height if all its children are absolute.

position: fixed

A fixed element is removed from the document flow and positioned relative to the browser viewport — not any parent element. It stays in the same screen position even as the user scrolls.

.sticky-nav {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 100;
  background: #0f111a;
}

/* Floating action button — bottom right corner */
.fab {
  position: fixed;
  bottom: 24px;
  right: 24px;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  z-index: 200;
}

/* Modal overlay — covers entire viewport */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 300;
}

Fixed elements and transforms: if an ancestor element has a transform, filter, or will-change property applied, it creates a new containing block that overrides the viewport. Fixed elements inside a transformed ancestor no longer behave as viewport-fixed. This is a common source of sticky nav bugs.

position: sticky

Sticky is a hybrid of relative and fixed. The element behaves like relative as the user scrolls down the page. When it reaches the offset threshold you define, it sticks in place and behaves like fixed. When its parent container scrolls off screen, the sticky element goes with it.

.sticky-header {
  position: sticky;
  top: 0; /* sticks when it reaches the top of the viewport */
  background: #0f111a;
  z-index: 10;
}

/* Sticky table header */
thead th {
  position: sticky;
  top: 0;
  background: #1a1d27;
}

/* Sidebar that sticks while scrolling page content */
.sidebar {
  position: sticky;
  top: 80px; /* accounts for a fixed navbar height */
  align-self: flex-start; /* required in flex layouts */
}

Why sticky is not working

There are three common reasons position: sticky fails silently:

/* Problem 1: no offset value set */
.header {
  position: sticky;
  /* missing top, bottom, left, or right — sticky will not activate */
}

/* Problem 2: parent has overflow: hidden or overflow: auto */
.parent {
  overflow: hidden; /* this kills sticky on children */
}

/* Problem 3: parent is not tall enough */
/* sticky only works while the parent is in view.
   if the parent is the same height as the sticky element,
   there is no room to scroll within it */

Quick fix checklist for sticky: confirm top is set, check that no ancestor has overflow: hidden or overflow: auto, and make sure the parent is significantly taller than the sticky element.

The offset properties: top, right, bottom, left

The four offset properties work differently depending on the position value:

PropertyWhat it does
topDistance from the top edge of the containing block (or viewport for fixed)
rightDistance from the right edge
bottomDistance from the bottom edge
leftDistance from the left edge
insetShorthand for all four: inset: 0 sets all to 0 at once

For relative, positive top pushes the element down from its normal position and positive left pushes it right. For absolute and fixed, the values define the gap between the element's edge and the containing block's edge.

/* relative: offset from natural position */
.icon {
  position: relative;
  top: -2px; /* nudge up 2px to align with text baseline */
}

/* absolute: gap from the containing block edges */
.tooltip {
  position: absolute;
  top: 100%; /* just below the parent's bottom edge */
  left: 0;
  margin-top: 8px;
}

Practical patterns

Notification badge on an icon

.icon-wrapper {
  position: relative;
  display: inline-flex;
}

.badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  background: #ef4444;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
}

Sticky navigation with page content offset

.nav {
  position: sticky;
  top: 0;
  height: 64px;
  z-index: 100;
}

/* Push page content down so it is not hidden under the nav */
.page-content {
  padding-top: 64px;
}

/* Alternative: use fixed nav and add body padding */
.nav-fixed {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 64px;
  z-index: 100;
}

body {
  padding-top: 64px;
}

Tooltip above a button

.btn-wrapper {
  position: relative;
  display: inline-block;
}

.tooltip {
  position: absolute;
  bottom: calc(100% + 8px); /* 8px above the button */
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  background: #1e2235;
  color: #e2e8f0;
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 13px;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.btn-wrapper:hover .tooltip {
  opacity: 1;
}

Full-screen modal dialog

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 400;
}

.modal-dialog {
  position: relative; /* for close button positioning */
  background: #1a1d27;
  border-radius: 12px;
  padding: 32px;
  max-width: 480px;
  width: 100%;
}

.modal-close {
  position: absolute;
  top: 16px;
  right: 16px;
}

FAQ

What is the difference between position absolute and position fixed?

position: absolute is placed relative to its nearest positioned ancestor. If you scroll the page, an absolutely positioned element scrolls with the content. position: fixed is always placed relative to the browser viewport and stays in the same spot even when the user scrolls. Use absolute for tooltips, dropdowns, and overlays inside a container. Use fixed for sticky navigation bars and persistent UI elements.

Why is my position absolute element not positioning correctly?

The most common cause is a missing positioned ancestor. An absolutely positioned element looks for the nearest ancestor with position set to relative, absolute, fixed, or sticky. If none exists, it positions itself relative to the root html element. Add position: relative to the parent you want to position against.

Does position absolute remove an element from the document flow?

Yes. Both position: absolute and position: fixed remove an element from normal document flow entirely. The element no longer occupies space in the layout and surrounding elements reflow as if it does not exist. position: relative and position: sticky keep the element in the normal flow.

How does position sticky work?

A sticky element behaves like position: relative until it hits a scroll threshold defined by top, bottom, left, or right. At that point it sticks in place, but only within its parent container. When the parent scrolls out of view, the sticky element goes with it. You must set at least one offset property (usually top) or the element will not stick.

What is the difference between position relative and position absolute?

position: relative keeps the element in normal document flow but lets you offset it from its natural position. The original space is preserved. position: absolute removes the element from the flow entirely and positions it relative to the nearest positioned ancestor. No space is reserved for it in the layout.

When should I use position fixed vs position sticky?

Use position: fixed when the element must always be visible regardless of scroll position and parent container, such as a chat widget or cookie banner. Use position: sticky when the element should scroll normally until it reaches a threshold, then stick only within its parent, such as a sticky table header or a sidebar that stops at the footer.

Generate CSS animations with transforms

Our CSS Animation Generator lets you build keyframe animations using translate, rotate, and scale — properties that work alongside position for powerful UI effects.

Open Animation Generator →