Ad – 728×90
📐 CSS Layout

CSS Position – Placing Elements Exactly Where You Want

Normal document flow places elements one after another. The position property breaks elements out of that flow — letting you place them precisely on the page, layer them on top of each other, fix them to the viewport, or make them stick during scroll. It is one of the most powerful and most misunderstood properties in CSS. In this lesson you will learn all five position values, when to use each one, how offset properties work, and how to control stacking order with z-index.

⏱️ 22 min read 🎯 Beginner 📅 Updated 2026 👁️ Lesson 2 of 6

The Five Position Values

CSS – All position values
position: static;    /* default — in normal flow, offsets have no effect */
position: relative;  /* in normal flow + offset from its own position */
position: absolute;  /* removed from flow, positioned inside nearest non-static ancestor */
position: fixed;     /* removed from flow, positioned relative to the viewport */
position: sticky;    /* in flow until threshold, then fixed within its scroll container */

position: static (Default)

Every element is static by default. It sits in normal document flow. top, left, right, bottom, and z-index have no effect on static elements.

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

/* Reset positioning from a more specific rule */
.no-position { position: static; }

position: relative

The element stays in normal flow but can be offset from its natural position using top, left, right, bottom. The space it originally occupied is preserved — other elements don't fill it.

CSS
.shifted {
  position: relative;
  top: 20px;     /* move 20px down from where it would be */
  left: 30px;    /* move 30px right from where it would be */
}

/* Most important use: creating a positioning context for children */
.card {
  position: relative;  /* children with position: absolute reference this */
}

/* Slight nudge for visual alignment */
.icon {
  position: relative;
  top: 2px;  /* nudge 2px down to optically align with text */
}
💡
Most common use of position: relative

You will use position: relative far more as a positioning context for absolutely positioned children than as an actual offset. Add it to any parent you want to be the "containing block" for its absolutely positioned children.

position: absolute

The element is removed from normal flow — surrounding elements act as if it doesn't exist. It is positioned relative to its nearest ancestor that has position set to anything other than static. If no such ancestor exists, it positions relative to the <html> element (the initial containing block).

CSS – absolute positioning pattern
/* Parent: create the positioning context */
.card {
  position: relative;
  padding: 20px;
}

/* Child: position inside the card */
.badge {
  position: absolute;
  top: 12px;
  right: 12px;
  background: #e63946;
  color: white;
  padding: 2px 8px;
  border-radius: 20px;
  font-size: .75rem;
}

/* Cover entire parent (overlay) */
.overlay {
  position: absolute;
  inset: 0;  /* shorthand for top:0; right:0; bottom:0; left:0 */
  background: rgba(0, 0, 0, 0.5);
}
absolute
bottom-left
absolute
top-right
centered

Common absolute positioning patterns

CSS – Patterns
/* Center absolutely positioned element */
.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* Pin to a corner */
.top-right  { position: absolute; top:  0; right:  0; }
.top-left   { position: absolute; top:  0; left:   0; }
.bottom-right { position: absolute; bottom: 0; right: 0; }

/* Fill entire parent */
.fill {
  position: absolute;
  inset: 0;   /* = top:0; right:0; bottom:0; left:0 */
}

/* Tooltip above an element */
.tooltip {
  position: absolute;
  bottom: calc(100% + 8px);  /* 8px above the parent */
  left: 50%;
  transform: translateX(-50%);
}
Ad – 336×280

position: fixed

Like absolute, the element is removed from normal flow. But it is positioned relative to the viewport — it stays in the same place as the user scrolls.

CSS – Fixed positioning
/* Sticky header */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;      /* stretch to full width */
  height: 60px;
  background: white;
  z-index: 100;
  box-shadow: 0 2px 8px rgba(0,0,0,.1);
}

/* Compensate — body needs top padding to prevent content hiding under header */
body { padding-top: 60px; }

/* Cookie banner at bottom */
.cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #222;
  color: white;
  padding: 16px 24px;
  z-index: 200;
}

/* Back to top button */
.scroll-top {
  position: fixed;
  bottom: 24px;
  right: 24px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #1572B6;
  color: white;
  z-index: 50;
}
⚠️
Fixed positioning and transforms

If any ancestor element has a transform, filter, or perspective property set (other than none), the fixed element positions relative to that ancestor, not the viewport. This is a common gotcha — if your fixed element isn't sticking to the viewport, check for transformed ancestors.

position: sticky

A hybrid of relative and fixed. The element behaves as relative until it reaches a threshold (defined by top, left, etc.) while scrolling, then it "sticks" in that position within its scroll container — until the container scrolls out of view.

CSS – Sticky positioning
/* Sticky table header */
th {
  position: sticky;
  top: 0;               /* sticks when it reaches 0px from viewport top */
  background: #1572B6;
  color: white;
  z-index: 1;
}

/* Sticky sidebar */
.sidebar {
  position: sticky;
  top: 80px;            /* 80px gap from viewport top (for fixed header) */
  align-self: start;    /* needed in flex/grid — prevents sidebar stretching */
  max-height: calc(100vh - 100px);
  overflow-y: auto;
}

/* Sticky section labels in a list */
.section-label {
  position: sticky;
  top: 60px;
  background: var(--bg-secondary);
  padding: 8px 16px;
  font-weight: bold;
  border-bottom: 1px solid var(--border-color);
}
⚠️
sticky requires a threshold AND scroll room

You must set at least one of top, bottom, left, right — without it, sticky behaves like relative. The sticky element also only sticks within its parent container — once the parent scrolls off-screen, the sticky element goes with it. If your sticky element isn't working, check: (1) threshold is set, (2) parent isn't overflow: hidden, (3) there is enough scroll room.

Offset Properties: top, right, bottom, left, inset

CSS – Offset properties
/* Individual offsets */
top: 20px;        /* move from top edge of containing block */
right: 0;         /* align to right edge */
bottom: 24px;     /* offset from bottom */
left: 50%;        /* 50% from left edge */

/* Negative values pull in the opposite direction */
top: -10px;       /* move 10px above the top edge */
left: -20px;      /* move 20px to the left of the left edge */

/* Percentage — relative to the containing block's dimensions */
top: 50%;         /* 50% of containing block height */
left: 50%;        /* 50% of containing block width */

/* inset shorthand — logical equivalent of top/right/bottom/left */
inset: 0;                    /* all sides: 0 */
inset: 10px 20px;            /* top/bottom: 10px, left/right: 20px */
inset: 10px 20px 30px 40px; /* top right bottom left */

/* Logical inset variants */
inset-block: 0;    /* top + bottom */
inset-inline: 0;   /* left + right */

z-index – Stacking Order

z-index controls which positioned element appears on top when elements overlap. Higher values appear on top. Only works on elements with position other than static.

CSS – z-index
/* Simple stacking */
.background { position: absolute; z-index: 0; }
.content    { position: absolute; z-index: 1; }
.overlay    { position: absolute; z-index: 2; }

/* Common z-index scale for a design system */
:root {
  --z-base:    0;
  --z-raised:  1;
  --z-dropdown: 100;
  --z-sticky:   200;
  --z-fixed:    300;
  --z-modal:    400;
  --z-toast:    500;
}

/* Auto — removes element from stacking context */
z-index: auto;

/* Negative z-index — behind normal flow elements */
.watermark { position: absolute; z-index: -1; }
ℹ️
Stacking contexts

A stacking context is an isolated z-index universe. Elements inside a stacking context are stacked relative to each other, not to the global page. Stacking contexts are created by: position + z-index (not auto), opacity < 1, transform, filter, will-change, and several other properties. This is why a z-index of 9999 inside a transformed container may still appear behind an element with z-index 1 outside it.

📋 Summary

  • static — default. In normal flow. Offsets and z-index have no effect.
  • relative — in normal flow, offset from its own natural position. Mainly used to create a containing block for absolute children.
  • absolute — removed from flow. Positioned inside nearest non-static ancestor. Use for badges, tooltips, overlays.
  • fixed — removed from flow. Positioned relative to viewport. Stays during scroll. Use for sticky headers, modals, toasts.
  • sticky — relative until threshold, then fixed within scroll container. Use for sticky nav, table headers, sidebars.
  • inset — shorthand for top/right/bottom/left. inset: 0 fills the parent.
  • z-index — stacking order. Only works on positioned elements. Define a scale for your project.

Frequently Asked Questions

Why is my absolutely positioned element not in the right place? +

The most common cause: the element is not positioning relative to the parent you think it is. An absolutely positioned element positions relative to its nearest ancestor with any position value other than static. If none exists, it positions relative to <html>. Fix: add position: relative to the intended parent container. Then check your top/left/right/bottom values — positive top moves down from the top edge, positive left moves right from the left edge.

Why doesn't my sticky element stick? +

Four common causes: (1) No threshold set — you must have at least one of top, bottom, left, right. (2) Parent has overflow: hidden, overflow: auto, or overflow: scroll — this breaks sticky by confining it to the parent's scroll context. (3) Parent isn't tall enough — sticky only works while the parent is taller than the sticky element and in the viewport. (4) Parent has a set height equal to its content — no scroll room means no sticky effect.

What is the difference between fixed and sticky? +

fixed is always positioned relative to the viewport — it's removed from flow immediately and stays put regardless of DOM position. sticky participates in normal flow until it reaches the threshold during scroll, then temporarily fixes in place. Crucially, sticky is scoped to its parent container — once the parent scrolls out, the sticky element goes with it. Fixed stays on screen no matter what. Use fixed for global UI elements (headers, modals). Use sticky for in-page elements (table headers, sidebars, section labels).

Why does a high z-index not work? +

z-index only works on positioned elements (position other than static). If the element is static, z-index is ignored. The other common cause: stacking contexts. If the element is inside a stacking context (created by transform, opacity, filter, etc.), its z-index only competes with siblings in the same context — not the global page. Setting z-index: 9999 inside a transformed parent won't place it above elements outside that parent's stacking context.