CSS Positioning - Precise Element Placement

What You'll Learn

  • Understand the position property and its five values
  • Use static for normal document flow (default)
  • Offset elements with relative positioning
  • Remove elements from flow with absolute positioning
  • Create viewport-fixed elements with fixed positioning
  • Implement scroll-aware positioning with sticky
  • Control stacking order with z-index
  • Use positioning offset properties: top, right, bottom, left

Introduction to CSS Positioning

CSS positioning controls how elements are placed on the page and whether they follow the normal document flow. The position property has five values, each creating different positioning behaviors and establishing different positioning contexts.

Once an element has a position value other than static, you can use offset properties (top, right, bottom, left) to precisely place it. Understanding positioning is essential for creating overlays, tooltips, modals, sticky headers, and complex layouts.

position: static - Default Behavior

Static positioning is the default for all elements. Elements with position: static follow the normal document flow, and offset properties have no effect.

/* Default positioning */ position: static; /* Element follows normal flow */ /* These have NO effect on static elements */ top: 20px; /* Ignored */ left: 10px; /* Ignored */ z-index: 10; /* Ignored */ /* Practical examples */ /* Explicit static (rarely needed) */ .reset-position { position: static; } /* Useful to override inherited positioning */ /* Most elements are static by default */ div, p, section, article { /* position: static; (implicit) */ }

position: relative - Offset from Original Position

Relative positioning offsets an element from where it would normally be, while keeping its original space in the document flow. The element becomes a positioning context for absolutely positioned children.

/* Offset from normal position */ position: relative; top: 20px; /* Move down 20px */ left: 30px; /* Move right 30px */ /* Other elements behave as if this element is still in its original position */ /* Practical examples */ /* Nudge an icon slightly */ .icon { position: relative; top: 2px; } /* Create positioning context for tooltip */ .tooltip-container { position: relative; } .tooltip { position: absolute; top: 100%; left: 50%; transform: translateX(-50%); } /* Overlap elements slightly */ .card { position: relative; top: -20px; } /* Pulls card up over previous element */ /* Layer stacking */ .foreground { position: relative; z-index: 10; } /* Bring to front without removing from flow */

position: absolute - Positioned Ancestor Context

Absolute positioning removes an element from the normal document flow and positions it relative to its nearest positioned ancestor (or the document if none exists).

/* Position relative to nearest positioned ancestor */ position: absolute; top: 0; right: 0; /* Element removed from flow - doesn't affect layout of other elements */ /* Practical examples */ /* Close button in corner of modal */ .modal { position: relative; } .close-button { position: absolute; top: 1rem; right: 1rem; } /* Overlay covering entire container */ .overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; /* or: width: 100%; height: 100%; */ background: rgba(0, 0, 0, 0.5); } /* Badge on avatar */ .avatar { position: relative; } .badge { position: absolute; top: -5px; right: -5px; background: red; border-radius: 50%; } /* Centered element */ .centered { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Tooltip positioned below trigger */ .tooltip { position: absolute; top: calc(100% + 8px); left: 0; }

position: fixed - Viewport Positioning

Fixed positioning positions an element relative to the viewport, removing it from document flow. It stays in place even when scrolling.

/* Position relative to viewport */ position: fixed; top: 0; left: 0; right: 0; /* Element removed from flow, stays fixed during scroll */ /* Practical examples */ /* Fixed header */ .header { position: fixed; top: 0; left: 0; right: 0; background: white; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 100; } /* Remember to add padding to body */ body { padding-top: 60px; } /* Prevents content from hiding under fixed header */ /* Back to top button */ .back-to-top { position: fixed; bottom: 2rem; right: 2rem; z-index: 1000; } /* Fixed sidebar */ .sidebar { position: fixed; top: 0; left: 0; bottom: 0; width: 250px; overflow-y: auto; } .main-content { margin-left: 250px; } /* Modal overlay */ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 1000; } /* Cookie banner */ .cookie-banner { position: fixed; bottom: 0; left: 0; right: 0; }

position: sticky - Hybrid Positioning

Sticky positioning is a hybrid of relative and fixed. Elements are relatively positioned until they reach a threshold during scrolling, then become fixed.

/* Sticky positioning */ position: sticky; top: 0; /* Sticks when scrolled to top: 0 */ /* Element is relative until threshold, then becomes fixed within its container */ /* Practical examples */ /* Sticky table headers */ thead th { position: sticky; top: 0; background: white; z-index: 10; } /* Sticky section headers */ .section-header { position: sticky; top: 60px; /* Below fixed navbar */ background: white; z-index: 5; } /* Sticky sidebar while scrolling */ .sidebar-widget { position: sticky; top: 2rem; } /* Sticky footer within container */ .sticky-footer { position: sticky; bottom: 0; background: white; } /* Table of contents */ .toc { position: sticky; top: 20px; max-height: calc(100vh - 40px); overflow-y: auto; } /* Important notes */ /* Parent must NOT have overflow: hidden */ .parent { /* overflow: hidden; ❌ Breaks sticky */ overflow: visible; /* ✅ Allows sticky */ } /* Sticky element must have threshold */ .sticky { position: sticky; /* top: 0; ✅ Required */ }

Offset Properties - top, right, bottom, left

Once an element is positioned (not static), use offset properties to control its placement. These work differently depending on the position type.

/* Single direction */ top: 20px; right: 10px; bottom: 0; left: 50%; /* Multiple directions */ top: 0; left: 0; /* Positioned at top-left */ /* All four sides (stretch to fill) */ top: 0; right: 0; bottom: 0; left: 0; /* Fills entire positioning context */ /* Different behaviors */ /* Relative: offset from original position */ .relative { position: relative; top: 10px; /* Move down from where it was */ left: -20px; /* Move left from where it was */ } /* Absolute: position within containing block */ .absolute { position: absolute; top: 0; /* 0px from top of positioned parent */ right: 0; /* 0px from right of positioned parent */ } /* Fixed: position within viewport */ .fixed { position: fixed; bottom: 20px; /* 20px from bottom of viewport */ right: 20px; /* 20px from right of viewport */ } /* Sticky: threshold for sticking */ .sticky { position: sticky; top: 0; /* Stick when reaching top of container */ } /* Practical examples */ /* Corner positioning */ .top-left { top: 0; left: 0; } .top-right { top: 0; right: 0; } .bottom-left { bottom: 0; left: 0; } .bottom-right { bottom: 0; right: 0; } /* Centered with absolute */ .centered { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } /* Stretch to fill */ .fill { position: absolute; inset: 0; /* Shorthand for top, right, bottom, left: 0 */ } /* Negative values work too */ .pull-up { position: relative; top: -20px; /* Pull up */ }

z-index - Stacking Order

Control stacking order of positioned elements with z-index. Higher values appear on top.

/* Stacking order */ z-index: 1; /* Default-ish (actually auto) */ z-index: 10; /* Above z-index: 1 */ z-index: 100; /* Above z-index: 10 */ z-index: -1; /* Behind default */ /* Only works on positioned elements */ .positioned { position: relative; /* or absolute, fixed, sticky */ z-index: 10; } .static { position: static; z-index: 10; /* ❌ No effect on static elements */ } /* Practical examples */ /* Common z-index scale */ .dropdown { z-index: 1000; } .sticky-header { z-index: 100; } .modal-backdrop { z-index: 1040; } .modal { z-index: 1050; } .tooltip { z-index: 1060; } .popover { z-index: 1070; } /* Behind other content */ .background-decoration { position: absolute; z-index: -1; } /* Stacking context */ .parent { position: relative; z-index: 1; } .child { position: absolute; z-index: 9999; /* Can't escape parent's stacking context */ } /* Layer an overlay */ .card { position: relative; } .card::before { content: ''; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.5); z-index: 1; } .card-content { position: relative; z-index: 2; } /* Content appears above overlay */ /* Debugging stacking */ .debug { outline: 2px solid red; z-index: 999999; }

Key Takeaways

  • position: static: Default; follows normal flow; offset properties ignored
  • position: relative: Offset from original position; space preserved in flow; creates positioning context
  • position: absolute: Removed from flow; positioned relative to nearest positioned ancestor
  • position: fixed: Removed from flow; positioned relative to viewport; stays put when scrolling
  • position: sticky: Hybrid; relative until threshold, then fixed within container
  • Positioned element: Any element with position other than static
  • Positioning context: Positioned elements create a positioning context for absolute children
  • Offset properties: top, right, bottom, left—only work on positioned elements
  • inset: Shorthand for top/right/bottom/left; inset: 0 fills container
  • z-index: Controls stacking order; only works on positioned elements; higher = on top
  • Stacking context: Created by z-index on positioned elements; isolates stacking order
  • Sticky gotcha: Broken by overflow: hidden on ancestors; needs explicit threshold
  • Fixed spacing: Remember to add padding/margin to prevent content hiding behind fixed elements