CSS Positioning
CSS positioning controls how elements are placed in the document flow. The position property accepts five values — static, relative, absolute, fixed, and sticky — each removing the element from normal flow to varying degrees and enabling offset properties (top, right, bottom, left).
Understanding positioning is essential for creating overlays, tooltips, modals, dropdown menus, and any UI that needs to break out of the normal document flow.
| 1 | .static { position: static; } /* default — normal flow */ |
| 2 | .relative { position: relative; } /* offset from normal position */ |
| 3 | .absolute { position: absolute; } /* relative to nearest positioned ancestor */ |
| 4 | .fixed { position: fixed; } /* relative to viewport */ |
| 5 | .sticky { position: sticky; } /* hybrid relative/fixed */ |
position: static is the default value for every element. Elements are positioned according to normal document flow — block elements stack vertically, inline elements flow horizontally. Offset properties have no effect on statically positioned elements.
| 1 | .box { |
| 2 | position: static; /* default — no special positioning */ |
| 3 | top: 20px; /* ignored — has no effect */ |
| 4 | left: 20px; /* ignored — has no effect */ |
| 5 | } |
info
position: relative keeps the element in normal flow but offsets it from its original position using top, right, bottom, or left. The element's original space is preserved — other elements do not reflow to fill the gap.
| 1 | .box { |
| 2 | position: relative; |
| 3 | top: 20px; /* pushed down 20px from original spot */ |
| 4 | left: 10px; /* pushed right 10px from original spot */ |
| 5 | } |
| 6 | |
| 7 | /* The original space is still occupied — other elements |
| 8 | cannot use the vacated space */ |
position: absolute removes the element entirely from normal flow — other elements behave as if it does not exist. It is positioned relative to its nearest positioned ancestor (any ancestor with position: relative, absolute, fixed, or sticky). If no positioned ancestor exists, it positions relative to the initial containing block (the viewport).
| 1 | .parent { |
| 2 | position: relative; /* establishes positioning context */ |
| 3 | } |
| 4 | |
| 5 | .child { |
| 6 | position: absolute; |
| 7 | top: 0; |
| 8 | right: 0; |
| 9 | /* pinned to top-right corner of .parent */ |
| 10 | } |
| 11 | |
| 12 | .overlay { |
| 13 | position: absolute; |
| 14 | inset: 0; /* shorthand for top:0; right:0; bottom:0; left:0 */ |
| 15 | /* covers entire positioned ancestor */ |
| 16 | } |
| 1 | .modal-backdrop { |
| 2 | position: fixed; |
| 3 | inset: 0; |
| 4 | background: rgba(0, 0, 0, 0.6); |
| 5 | z-index: 100; |
| 6 | } |
| 7 | |
| 8 | .modal-content { |
| 9 | position: absolute; |
| 10 | top: 50%; |
| 11 | left: 50%; |
| 12 | transform: translate(-50%, -50%); |
| 13 | /* perfectly centered within the backdrop */ |
| 14 | z-index: 101; |
| 15 | } |
warning
position: fixed removes an element from normal flow and positions it relative to the viewport (or the nearest ancestor with a transform, perspective, or filter property). Fixed elements stay in place when the page scrolls, making them ideal for sticky headers, floating action buttons, and persistent navigation.
| 1 | .navbar { |
| 2 | position: fixed; |
| 3 | top: 0; |
| 4 | left: 0; |
| 5 | right: 0; |
| 6 | height: 64px; |
| 7 | background: #0D0D0D; |
| 8 | border-bottom: 1px solid #222222; |
| 9 | z-index: 1000; |
| 10 | } |
| 11 | |
| 12 | /* Add padding to body so content doesn't hide behind navbar */ |
| 13 | body { |
| 14 | padding-top: 64px; |
| 15 | } |
| 16 | |
| 17 | .fab { |
| 18 | position: fixed; |
| 19 | bottom: 24px; |
| 20 | right: 24px; |
| 21 | width: 56px; |
| 22 | height: 56px; |
| 23 | border-radius: 50%; |
| 24 | background: #00FF41; |
| 25 | color: #0D0D0D; |
| 26 | box-shadow: 0 4px 12px rgba(0, 255, 65, 0.3); |
| 27 | z-index: 999; |
| 28 | } |
note
position: sticky is a hybrid of relative and fixed positioning. The element behaves as relatively positioned until it crosses a specified threshold, at which point it becomes fixed within its scrolling container. It is commonly used for sticky headers, table headers, and section labels.
| 1 | .section-header { |
| 2 | position: sticky; |
| 3 | top: 0; |
| 4 | /* becomes fixed at top: 0 when scrolled to that position */ |
| 5 | background: #0D0D0D; |
| 6 | z-index: 10; |
| 7 | } |
| 8 | |
| 9 | /* Sticky sidebar */ |
| 10 | .sidebar { |
| 11 | position: sticky; |
| 12 | top: 80px; /* accounts for fixed navbar height */ |
| 13 | height: calc(100vh - 80px); |
| 14 | overflow-y: auto; |
| 15 | } |
| 16 | |
| 17 | /* Sticky table headers */ |
| 18 | thead th { |
| 19 | position: sticky; |
| 20 | top: 0; |
| 21 | background: #0D0D0D; |
| 22 | z-index: 5; |
| 23 | } |
best practice
Every positioned element participates in a stacking context. The z-indexproperty controls the order within the same stacking context, but elements in different stacking contexts are compared by their parent context's z-index, not their own.
| 1 | .layer-1 { |
| 2 | position: relative; |
| 3 | z-index: 1; |
| 4 | } |
| 5 | |
| 6 | .layer-2 { |
| 7 | position: relative; |
| 8 | z-index: 2; /* appears above layer-1 */ |
| 9 | } |
| 10 | |
| 11 | /* Different stacking contexts */ |
| 12 | .parent-a { |
| 13 | position: relative; |
| 14 | z-index: 1; |
| 15 | } |
| 16 | |
| 17 | .parent-b { |
| 18 | position: relative; |
| 19 | z-index: 2; /* entire parent-b stack is above parent-a */ |
| 20 | } |
| 21 | |
| 22 | .child-of-a { |
| 23 | position: absolute; |
| 24 | z-index: 9999; /* still behind parent-b's children! */ |
| 25 | } |
| 26 | |
| 27 | .child-of-b { |
| 28 | position: absolute; |
| 29 | z-index: 1; /* appears above child-of-a */ |
info
Using transform for positioning is often better than top/left because it only triggers compositing, not layout. This is especially important for animation performance.
| 1 | .bad { |
| 2 | position: relative; |
| 3 | top: 100px; /* triggers layout recalculation */ |
| 4 | left: 100px; |
| 5 | } |
| 6 | |
| 7 | .good { |
| 8 | position: relative; |
| 9 | transform: translate(100px, 100px); /* compositor-only, 60fps */ |
| 10 | } |
| 11 | |
| 12 | /* Centering with transform */ |
| 13 | .center { |
| 14 | position: absolute; |
| 15 | top: 50%; |
| 16 | left: 50%; |
| 17 | transform: translate(-50%, -50%); |
| 18 | /* perfect centering regardless of element size */ |
| 19 | } |
warning
Tooltip
| 1 | .tooltip-container { |
| 2 | position: relative; |
| 3 | display: inline-block; |
| 4 | } |
| 5 | |
| 6 | .tooltip { |
| 7 | position: absolute; |
| 8 | bottom: calc(100% + 8px); |
| 9 | left: 50%; |
| 10 | transform: translateX(-50%); |
| 11 | padding: 6px 12px; |
| 12 | background: #1A1A2E; |
| 13 | color: #E0E0E0; |
| 14 | border: 1px solid #222222; |
| 15 | border-radius: 6px; |
| 16 | font-size: 12px; |
| 17 | white-space: nowrap; |
| 18 | opacity: 0; |
| 19 | pointer-events: none; |
| 20 | transition: opacity 0.2s ease; |
| 21 | } |
| 22 | |
| 23 | .tooltip-container:hover .tooltip { |
| 24 | opacity: 1; |
| 25 | } |
Modal
| 1 | .modal-overlay { |
| 2 | position: fixed; |
| 3 | inset: 0; |
| 4 | background: rgba(0, 0, 0, 0.7); |
| 5 | display: flex; |
| 6 | align-items: center; |
| 7 | justify-content: center; |
| 8 | z-index: 1000; |
| 9 | opacity: 0; |
| 10 | pointer-events: none; |
| 11 | transition: opacity 0.3s ease; |
| 12 | } |
| 13 | |
| 14 | .modal-overlay.active { |
| 15 | opacity: 1; |
| 16 | pointer-events: all; |
| 17 | } |
| 18 | |
| 19 | .modal { |
| 20 | background: #0D0D0D; |
| 21 | border: 1px solid #222222; |
| 22 | border-radius: 12px; |
| 23 | padding: 24px; |
| 24 | max-width: 480px; |
| 25 | width: 90%; |
| 26 | max-height: 80vh; |
| 27 | overflow-y: auto; |
| 28 | } |
Dropdown Menu
| 1 | .dropdown { |
| 2 | position: relative; |
| 3 | display: inline-block; |
| 4 | } |
| 5 | |
| 6 | .dropdown-menu { |
| 7 | position: absolute; |
| 8 | top: 100%; |
| 9 | left: 0; |
| 10 | min-width: 200px; |
| 11 | background: #0D0D0D; |
| 12 | border: 1px solid #222222; |
| 13 | border-radius: 8px; |
| 14 | padding: 4px; |
| 15 | opacity: 0; |
| 16 | transform: translateY(-4px); |
| 17 | pointer-events: none; |
| 18 | transition: opacity 0.2s ease, transform 0.2s ease; |
| 19 | z-index: 50; |
| 20 | } |
| 21 | |
| 22 | .dropdown:hover .dropdown-menu { |
| 23 | opacity: 1; |
| 24 | transform: translateY(0); |
| 25 | pointer-events: all; |
| 26 | } |
| 27 | |
| 28 | .dropdown-item { |
| 29 | padding: 8px 12px; |
| 30 | border-radius: 4px; |
| 31 | color: #A0A0A0; |
| 32 | font-size: 13px; |
| 33 | cursor: pointer; |
| 34 | transition: background 0.15s ease; |
| 35 | } |
| 36 | |
| 37 | .dropdown-item:hover { |
| 38 | background: #1A1A2E; |
| 39 | color: #E0E0E0; |
| 40 | } |
Terminal Overlay
| 1 | .terminal-overlay { |
| 2 | position: fixed; |
| 3 | inset: 0; |
| 4 | background: rgba(13, 13, 13, 0.95); |
| 5 | display: flex; |
| 6 | align-items: center; |
| 7 | justify-content: center; |
| 8 | z-index: 9999; |
| 9 | } |
| 10 | |
| 11 | .terminal-window { |
| 12 | width: 80%; |
| 13 | max-width: 800px; |
| 14 | background: #0D0D0D; |
| 15 | border: 1px solid #222222; |
| 16 | border-radius: 8px; |
| 17 | overflow: hidden; |
| 18 | } |
| 19 | |
| 20 | .terminal-header { |
| 21 | display: flex; |
| 22 | align-items: center; |
| 23 | gap: 8px; |
| 24 | padding: 12px 16px; |
| 25 | background: #1A1A2E; |
| 26 | border-bottom: 1px solid #222222; |
| 27 | } |
| 28 | |
| 29 | .terminal-dot { |
| 30 | width: 10px; |
| 31 | height: 10px; |
| 32 | border-radius: 50%; |
| 33 | } |
| 34 | |
| 35 | .terminal-dot.red { background: #FF5F56; } |
| 36 | .terminal-dot.yellow { background: #FFBD2E; } |
| 37 | .terminal-dot.green { background: #27C93F; } |
| 38 | |
| 39 | .terminal-body { |
| 40 | padding: 24px; |
| 41 | color: #00FF41; |
| 42 | font-family: monospace; |
| 43 | font-size: 14px; |
| 44 | line-height: 1.8; |
| 45 | } |
| Browser | static/relative/absolute/fixed | sticky |
|---|---|---|
| Chrome | ✓ | ✓ 56+ |
| Firefox | ✓ | ✓ 59+ |
| Safari | ✓ | ✓ 13+ |
| Edge | ✓ | ✓ 16+ |