CSS Positioning and Layering
1. CSS Position Property (static, relative, absolute, fixed, sticky)
| Position Value | Behavior | Offset Properties | Flow Impact |
|---|---|---|---|
| static | Normal document flow (default) | Ignored (top, right, bottom, left have no effect) | In flow, takes up space |
| relative | Positioned relative to itself | Active (offsets from normal position) | In flow, original space reserved |
| absolute | Positioned relative to nearest positioned ancestor | Active (offsets from ancestor edges) | Out of flow, no space reserved |
| fixed | Positioned relative to viewport | Active (offsets from viewport edges) | Out of flow, no space reserved |
| sticky | Hybrid: relative until threshold, then fixed | Active (defines sticky threshold) | In flow until sticky, then acts like fixed |
| Property | Values | Description | Notes |
|---|---|---|---|
| position | static | relative | absolute | fixed | sticky |
Sets positioning scheme | Default: static |
| top | length | % |
Offset from top edge | Negative values move up |
| right | length | % |
Offset from right edge | Negative values move right |
| bottom | length | % |
Offset from bottom edge | Negative values move down |
| left | length | % |
Offset from left edge | Negative values move left |
| inset | top right bottom left |
Shorthand for all 4 offsets | inset: 0; = all edges at 0 |
| inset-block-start | length | % |
Logical top offset | Writing mode aware |
| inset-block-end | length | % |
Logical bottom offset | Writing mode aware |
| inset-inline-start | length | % |
Logical left offset | Writing mode aware |
| inset-inline-end | length | % |
Logical right offset | Writing mode aware |
Example: Position values comparison
/* Static (default) - normal flow */
.static {
position: static;
top: 20px; /* Has no effect */
}
/* Relative - offset from normal position */
.relative {
position: relative;
top: 20px; /* Moves down 20px from normal position */
left: 10px; /* Moves right 10px */
/* Original space in layout is preserved */
}
/* Absolute - positioned relative to nearest positioned ancestor */
.absolute {
position: absolute;
top: 0; /* Top of positioned parent */
right: 0; /* Right edge of positioned parent */
/* Removed from document flow */
}
/* Fixed - positioned relative to viewport */
.fixed {
position: fixed;
bottom: 20px; /* 20px from bottom of viewport */
right: 20px; /* 20px from right of viewport */
/* Stays in place during scrolling */
}
/* Sticky - relative until scroll threshold */
.sticky {
position: sticky;
top: 0; /* Sticks to top when scrolled to */
/* In flow until sticky threshold is reached */
}
Example: Practical positioning patterns
/* Centered absolute positioning */
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Full coverage overlay */
.overlay {
position: fixed;
inset: 0; /* Shorthand for top: 0; right: 0; bottom: 0; left: 0; */
/* Or: top: 0; right: 0; bottom: 0; left: 0; */
}
/* Sticky header with shadow on scroll */
.header {
position: sticky;
top: 0;
z-index: 100;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Absolute positioning with explicit container */
.container {
position: relative; /* Establishes positioning context */
}
.badge {
position: absolute;
top: -10px;
right: -10px;
}
Note: An element is "positioned" if its position is anything
other than
static. Absolute and fixed positioned elements are removed from normal document flow.
2. Z-index and Stacking Context
| Property | Values | Description | Requirement |
|---|---|---|---|
| z-index | integer | auto |
Stacking order within context | Requires positioned element or flex/grid item |
| auto (default) | z-index: auto; |
Same level as parent, no new context | - |
| Positive | z-index: 1; |
Above elements with lower or auto z-index | Creates stacking context |
| Negative | z-index: -1; |
Behind parent's background | Creates stacking context |
| Zero | z-index: 0; |
Same level, but creates context | Creates stacking context |
| Stacking Context Created By | Trigger Condition | Common Use Case |
|---|---|---|
| Root element | <html> |
Global stacking context |
| position + z-index | position: relative/absolute/fixed/sticky + z-index: <integer> |
Positioned element layering |
| flex/grid item + z-index | Flex/grid item with z-index: <integer> |
Flexbox/Grid item ordering |
| opacity < 1 | opacity: 0.99; |
Transparent elements |
| transform | transform: translateZ(0); |
3D transforms, GPU acceleration |
| filter | filter: blur(5px); |
Visual effects |
| mix-blend-mode | mix-blend-mode: multiply; |
Blend modes |
| isolation | isolation: isolate; |
Force new stacking context |
| will-change | will-change: transform; |
Performance optimization hint |
| contain | contain: layout; or contain: paint; |
Containment optimization |
Example: Stacking context basics
/* Parent creates stacking context */
.parent {
position: relative;
z-index: 1;
}
/* Child cannot escape parent's context */
.child {
position: absolute;
z-index: 999999; /* Only stacks within parent, not globally */
}
/* Sibling with higher z-index appears above entire parent */
.sibling {
position: relative;
z-index: 2; /* Appears above parent and all its children */
}
/* Force new stacking context without positioning */
.isolated {
isolation: isolate; /* Creates context without other side effects */
}
/* Common pitfall - opacity creates context */
.modal-overlay {
opacity: 0.95; /* Creates stacking context! */
}
.modal-content {
z-index: 1000; /* Only applies within overlay context */
}
Example: Debugging stacking issues
/* Z-index scale for consistency */
:root {
--z-base: 0;
--z-dropdown: 100;
--z-sticky: 200;
--z-modal-overlay: 1000;
--z-modal-content: 1001;
--z-popover: 2000;
--z-tooltip: 3000;
}
.dropdown { z-index: var(--z-dropdown); }
.modal-overlay { z-index: var(--z-modal-overlay); }
.modal-content { z-index: var(--z-modal-content); }
/* Identify stacking contexts in DevTools */
.debug-stacking-context {
position: relative;
z-index: 0;
outline: 2px solid red; /* Visual indicator */
}
/* Prevent accidental context creation */
.no-context {
/* Avoid: opacity < 1, transform, filter, etc. */
/* Use isolation: isolate when context is needed */
}
Warning: Child elements cannot escape parent's stacking context.
If parent has
z-index: 1, child with z-index: 9999 still appears behind sibling with
z-index: 2.
3. Sticky Positioning (position: sticky) Patterns
| Pattern | CSS | Description | Use Case |
|---|---|---|---|
| Sticky Header | position: sticky; top: 0; |
Header sticks to top of viewport | Navigation bars, table headers |
| Sticky Footer | position: sticky; bottom: 0; |
Footer sticks to bottom of container | Action buttons, status bars |
| Sticky Sidebar | position: sticky; top: 20px; |
Sidebar sticks with offset from top | Table of contents, filters |
| Stacked Sticky | Multiple sticky elements with different top values |
Elements stack on top of each other | Layered headers, tabs |
| Sticky Table Header | thead th { position: sticky; top: 0; } |
Table header row stays visible | Data tables, comparison tables |
| Horizontal Sticky | position: sticky; left: 0; |
Column sticks to left during horizontal scroll | First column in wide tables |
Example: Sticky header with shadow on scroll
/* Basic sticky header */
.header {
position: sticky;
top: 0;
background: white;
z-index: 100;
}
/* Add shadow when stuck (requires JS or scroll-timeline) */
.header.is-stuck {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* CSS-only shadow using pseudo-element */
.header {
position: sticky;
top: 0;
background: white;
z-index: 100;
}
.header::after {
content: '';
position: absolute;
bottom: -8px;
left: 0;
right: 0;
height: 8px;
background: linear-gradient(to bottom, rgba(0,0,0,0.1), transparent);
opacity: 0;
transition: opacity 0.3s;
}
/* Stacked sticky sections */
.section-header {
position: sticky;
top: 60px; /* Below main header */
background: #f5f5f5;
z-index: 90;
}
Example: Sticky table headers and columns
/* Sticky table header */
table {
border-collapse: collapse;
}
thead th {
position: sticky;
top: 0;
background: white;
z-index: 2;
box-shadow: 0 1px 0 #ddd;
}
/* Sticky first column */
tbody td:first-child,
thead th:first-child {
position: sticky;
left: 0;
background: white;
z-index: 1;
}
/* Corner cell needs highest z-index */
thead th:first-child {
z-index: 3; /* Above both sticky header and column */
}
/* Sticky sidebar */
.sidebar {
position: sticky;
top: 80px; /* Below header */
max-height: calc(100vh - 100px);
overflow-y: auto;
}
/* Sticky footer CTA */
.cta-bar {
position: sticky;
bottom: 0;
background: white;
box-shadow: 0 -2px 8px rgba(0,0,0,0.1);
padding: 1rem;
}
Note: Sticky positioning requires ancestor with scrollable
overflow. Element sticks within its containing block. Use
z-index to control stacking of
multiple sticky elements.
6.4 CSS Anchor Positioning (CSS4) NEW EXPERIMENTAL
| Property | Syntax | Description | Use Case |
|---|---|---|---|
| anchor-name | anchor-name: --my-anchor; |
Names element as anchor point | Define anchor for tooltip/popover |
| position-anchor | position-anchor: --my-anchor; |
Links positioned element to anchor | Attach tooltip to button |
| anchor() | top: anchor(bottom); |
Positions relative to anchor edge | Place below anchor element |
| anchor-default | anchor-default: --fallback; |
Fallback anchor if primary unavailable | Graceful degradation |
| Anchor Sides | top | right | bottom | left | start | end | center |
Edge or position to reference | Flexible positioning |
| position-fallback | @position-fallback |
Alternative positions if out of viewport | Smart tooltip positioning |
Example: Basic anchor positioning
/* Define anchor element */
.button {
anchor-name: --my-button;
}
/* Position tooltip relative to anchor */
.tooltip {
position: absolute;
position-anchor: --my-button;
/* Position below button */
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
/* Or position above */
/* bottom: anchor(top); */
}
/* With offset */
.popover {
position: absolute;
position-anchor: --trigger;
top: calc(anchor(bottom) + 8px); /* 8px gap */
left: anchor(left);
}
/* Anchor to specific side */
.menu {
position: absolute;
position-anchor: --menu-button;
top: anchor(top);
left: anchor(right, 10px); /* 10px from right edge */
}
Example: Position fallback for viewport boundaries
/* Define fallback positions */
@position-fallback --tooltip-fallback {
/* Try: below, centered */
@try {
top: anchor(bottom);
left: anchor(center);
transform: translateX(-50%);
}
/* If overflow, try: above, centered */
@try {
bottom: anchor(top);
left: anchor(center);
transform: translateX(-50%);
}
/* If still overflow, try: right side */
@try {
top: anchor(center);
left: anchor(right);
transform: translateY(-50%);
}
/* Last resort: left side */
@try {
top: anchor(center);
right: anchor(left);
transform: translateY(-50%);
}
}
.tooltip {
position: absolute;
position-anchor: --button;
position-fallback: --tooltip-fallback;
}
Note: CSS Anchor Positioning is experimental (Chrome 125+ behind
flag). Provides native solution for tooltips, popovers, and dropdowns without JavaScript. Browser support: Chrome 125+ (flag)
5. Scroll-driven Positioning Techniques
| Technique | Implementation | Description | Browser Support |
|---|---|---|---|
| Sticky Positioning | position: sticky; |
CSS-native scroll-triggered positioning | All modern browsers |
| Scroll Snap | scroll-snap-type |
Snap to positions during scroll | All modern browsers |
| Scroll Timeline | animation-timeline: scroll() |
Animate based on scroll progress | Chrome 115+, Edge 115+ |
| View Timeline | animation-timeline: view() |
Animate based on element visibility | Chrome 115+, Edge 115+ |
| IntersectionObserver | JavaScript API | Detect element visibility changes | All modern browsers |
| ScrollTrigger (GSAP) | Third-party library | Advanced scroll-based animations | Library-dependent |
Example: Scroll-driven animations
/* Scroll-linked animation (CSS Scroll-driven Animations) */
@keyframes fade-in {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
.scroll-reveal {
animation: fade-in linear;
animation-timeline: view(); /* Animates as element enters view */
animation-range: entry 0% entry 100%; /* Start/end within entry range */
}
/* Parallax with scroll timeline */
@keyframes parallax {
to { transform: translateY(-100px); }
}
.parallax-bg {
animation: parallax linear;
animation-timeline: scroll(); /* Linked to scroll position */
}
/* Progress bar based on scroll */
.progress-bar {
position: fixed;
top: 0;
left: 0;
height: 4px;
background: blue;
transform-origin: left;
animation: grow linear;
animation-timeline: scroll();
}
@keyframes grow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
Example: Scroll snap positioning
/* Vertical scroll snap */
.scroll-container {
scroll-snap-type: y mandatory; /* Vertical snapping required */
overflow-y: scroll;
height: 100vh;
}
.scroll-section {
scroll-snap-align: start; /* Snap to top of section */
scroll-snap-stop: always; /* Force stop at each section */
height: 100vh;
}
/* Horizontal scroll snap (carousel) */
.carousel {
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
gap: 1rem;
}
.carousel-item {
flex: 0 0 300px;
scroll-snap-align: center; /* Center each item */
}
/* Scroll padding for sticky headers */
html {
scroll-padding-top: 80px; /* Offset for sticky header */
}
/* Smooth scroll */
html {
scroll-behavior: smooth;
}
Example: Position fixed with scroll context
/* Fixed positioning that respects scroll container */
.scroll-container {
overflow: auto;
height: 100vh;
}
/* Fixed within scroll container (not viewport) */
.fixed-in-container {
position: sticky; /* Use sticky instead of fixed */
top: 0;
z-index: 10;
}
/* Fade header on scroll (with JS toggle) */
.header {
position: fixed;
top: 0;
width: 100%;
transition: transform 0.3s, box-shadow 0.3s;
}
.header.scrolled {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header.hidden {
transform: translateY(-100%);
}
/* Parallax sections with different speeds */
.parallax-slow {
animation: parallax linear;
animation-timeline: scroll();
}
@keyframes parallax {
to { transform: translateY(calc(-100vh * 0.3)); }
}
Positioning Best Practices
- Use relative positioning to establish context for absolute children
- Use sticky positioning for headers/navigation without JavaScript
- Define z-index scale as CSS variables for consistency
- Use
inset: 0;shorthand for full coverage positioning - Avoid creating accidental stacking contexts (opacity, transform, filter)
- Use
isolation: isolateto explicitly create stacking contexts - Test sticky positioning with sufficient scrollable area
- Use logical properties (inset-block, inset-inline) for internationalization
- Combine
position: fixedwithtransformcarefully (creates new containing block)