Skip to main content

Positioning & Stacking Cheatsheet

position Values Compared

ValueIn Flow?Positioned ByCommon Use
static✅ YesNormal flow (default)Default — don't need to set
relative✅ YesOffset from own positionParent for absolute children
absolute❌ NoNearest positioned ancestorTooltips, badges, overlays
fixed❌ NoViewportSticky nav, modal backdrop
sticky✅/❌Scroll position then viewportSticky headers, sidebar

inset Shorthand (Modern)

/* inset: top right bottom left (same clock as margin/padding) */
inset: 0; /* top:0; right:0; bottom:0; left:0 — fill positioned parent */
inset: 0 0 auto auto; /* top:0; right:0; bottom:auto; left:auto */
inset-block: 0; /* top + bottom */
inset-inline: 0; /* left + right */
inset-block-start: 1rem; /* top */
inset-inline-end: 1rem; /* right (in LTR) */

Common Positioning Patterns

/* Overlay: fills positioned parent */
.c-overlay {
position: absolute;
inset: 0; /* All sides 0 */
background: rgba(0,0,0,0.5);
}

/* Badge: top-right corner of a card */
.c-badge {
position: absolute;
top: -0.5rem;
right: -0.5rem;
/* Or logical: */
inset-block-start: -0.5rem;
inset-inline-end: -0.5rem;
}
/* Parent must have position: relative */
.c-card { position: relative; }

/* Center within a positioned parent */
.c-centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

/* Modern center with anchor: */
.c-centered {
position: absolute;
inset: 0;
margin: auto;
width: fit-content;
height: fit-content;
}

/* Sticky navigation */
.c-nav {
position: sticky;
top: 0;
z-index: var(--z-nav);
/* Sticky only works within a scrollable parent */
/* Parent must not have overflow:hidden */
}

/* Fixed modal backdrop */
.c-modal__backdrop {
position: fixed;
inset: 0;
z-index: var(--z-modal-backdrop);
background: rgba(0,0,0,0.6);
}

/* Fixed bottom bar */
.c-toast-container {
position: fixed;
bottom: 1rem;
right: 1rem;
z-index: var(--z-toast);
}

z-index System

/* Define z-index scale in tokens */
:root {
--z-behind: -1;
--z-base: 0;
--z-raised: 10; /* Cards on hover */
--z-dropdown: 100; /* Dropdowns, tooltips */
--z-sticky: 200; /* Sticky nav */
--z-overlay: 300; /* Page overlays */
--z-modal-backdrop:400; /* Modal backdrop */
--z-modal: 500; /* Modal window */
--z-notification: 600; /* Toasts, snackbars */
--z-toast: 700; /* Above everything */
}

/* Usage */
.c-nav { z-index: var(--z-sticky); }
.c-modal { z-index: var(--z-modal); }
.c-toast { z-index: var(--z-toast); }

Stacking Context — What Creates One

A stacking context is an isolated stacking layer.
z-index only competes within the same stacking context.

Created by:
position: relative/absolute/fixed/sticky + z-index != auto
opacity < 1
transform != none
filter != none
will-change: (any of the above)
isolation: isolate ← clean way to create one
mix-blend-mode != normal
contain: layout, paint, strict, content

Symptom: "my z-index:9999 is behind a z-index:1 element"
→ The low z-index element is in a parent stacking context
→ Fix: remove transform/opacity/filter from ancestor
→ Or: use isolation: isolate on parent to contain its own stack

isolation: isolate

/* Create a stacking context WITHOUT visual side effects */
.c-card-wrapper {
isolation: isolate; /* z-index race between children stays contained here */
}

/* Now children can use z-index freely without competing with page-level elements */
.c-card__badge { position: absolute; z-index: 10; }
.c-card__overlay { position: absolute; z-index: 1; }
/* These z-indexes don't interfere with .c-nav or .c-modal */

Sticky Gotchas

/* ❌ Sticky doesn't work when: */

/* 1. Parent has overflow:hidden, auto, or scroll */
.parent { overflow: hidden; } /* ← breaks sticky children */
/* Fix: remove overflow from parent, wrap inner content instead */

/* 2. Parent is too small (sticky has nowhere to "stick") */
/* Sticky works within its scroll container — parent must be tall enough */

/* 3. No top/bottom/left/right set */
.c-sticky { position: sticky; } /* ← not stuck to anything */
/* Fix: */
.c-sticky { position: sticky; top: 0; }

/* ✅ Sticky header pattern */
.c-nav {
position: sticky;
top: 0;
z-index: var(--z-sticky);
background: var(--color-surface); /* Must have background — transparent shows content under */
}

Containing Block Rules

All positioned elements are offset relative to their CONTAINING BLOCK:

position: static/relative → nearest block ancestor
position: absolute → nearest POSITIONED ancestor (not static)
position: fixed → viewport (usually)
position: sticky → scroll container

"Nearest positioned ancestor" = any ancestor with position != static
Common pattern: set position:relative on a parent to "trap" absolute children

will-change — Use Sparingly

/* Tell browser to prepare a compositing layer */
.c-modal { will-change: transform, opacity; }
/* Good for: complex animations on known elements */

/* ❌ Don't do blanket will-change */
* { will-change: transform; } /* Creates layers for EVERYTHING = memory waste */

/* Remove after animation */
.c-modal.animation-done { will-change: auto; }