Positioning — relative, absolute, fixed and sticky
IntermediatePositioning takes an element out of the normal flow, or anchors it while scrolling. The recurring question is always the same: positioned relative to what?
Overview
Four position values, and the one that trips everyone is absolute — because "absolute to what" has a precise answer: the nearest ancestor that is itself positioned. Forget to set position: relative on that ancestor and your dropdown anchors to the page instead of the button. Sticky is the newest and most useful of the four, and it fails silently in three specific situations that are worth memorising because the CSS looks perfectly correct when it does.
The Four Values
What each does to the flow, and what it is measured against.
/* static — the default. top/left/z-index do nothing. */
/* relative — stays in the flow and reserves its original space,
but is visually nudged. Its main job is to be a positioning
ancestor for something absolute inside it. */
.wrapper { position: relative }
/* absolute — REMOVED from the flow, positioned against the nearest
positioned ancestor (or the page if there is none). */
.badge { position: absolute; top: -.5rem; inset-inline-end: -.5rem }
/* fixed — removed from the flow, positioned against the VIEWPORT,
so it does not scroll. */
.fab { position: fixed; inset-block-end: 1rem; inset-inline-end: 1rem }
/* sticky — in the flow until it hits its threshold, then it stays. */
thead th { position: sticky; top: 0 }
/* inset is the shorthand for all four offsets */
.overlay { position: absolute; inset: 0 } /* fill the parent */
.modal { position: fixed; inset: 0; display: grid; place-items: center }
/* Centring something absolute */
.center { position: absolute; inset: 0; margin: auto; width: 200px;
height: 100px }Why Sticky Silently Fails
Three causes. The CSS looks right in all of them.
/* 1. No threshold. Sticky needs at least one of top/bottom/
left/right — without it there is nothing to stick to. */
.nav { position: sticky } /* does nothing */
.nav { position: sticky; top: 0 } /* works */
/* 2. An ancestor has overflow: hidden, auto or scroll. Sticky
positions within its nearest scrolling ancestor, and that
container becomes it. This is the most common cause, and the
overflow is often several levels up and set for an unrelated
reason. */
.some-wrapper { overflow: hidden } /* breaks sticky inside it */
/* 3. The parent is not tall enough. A sticky element can only move
within its PARENT's box, so if the parent is exactly as tall
as the element, there is nowhere to stick. */
.sidebar-parent { height: auto } /* not: align-items: stretch
making it exactly as tall */
/* Debugging order: check for a threshold, then walk up the tree in
DevTools looking for overflow, then check the parent's height. */
/* A common combination: a sticky header that offsets anchor links */
:target { scroll-margin-block-start: 5rem }
html { scroll-padding-block-start: 5rem }Anchoring a Popover
The pattern behind every dropdown, tooltip and menu — and the modern alternatives.
.dropdown { position: relative } /* the anchor */
.dropdown__menu {
position: absolute;
inset-block-start: 100%; /* directly below */
inset-inline-start: 0;
min-width: 100%;
}
/* The problem this hits: the menu is clipped by any ancestor with
overflow: hidden, and it cannot escape a low z-index stacking
context. That is exactly why component libraries render menus in
a portal at the end of the body. */
/* The platform is fixing this. The popover attribute renders in the
top layer, above everything, with no z-index and no portal: */
<button popovertarget="menu">Options</button>
<div id="menu" popover>…</div>
/* And CSS anchor positioning ties it back to its trigger: */
.trigger { anchor-name: --trigger }
.menu { position: absolute; position-anchor: --trigger;
top: anchor(bottom); left: anchor(left) }
/* Check support before relying on these; the portal pattern remains
the safe default in 2026. */Key Points to Remember
- 1An absolutely positioned element is measured against the nearest positioned ancestor, or the page if there is none
- 2inset: 0 fills the containing block and is the shorthand for all four offsets
- 3Sticky needs a threshold, an ancestor without overflow hidden/auto, and a parent taller than itself
- 4A sticky header needs scroll-padding so anchor links do not land underneath it
- 5Absolutely positioned menus are clipped by overflow and trapped by stacking contexts, which is why libraries use portals or the popover top layer
Interview Questions
Sign in to ask AriaWhat is position: absolute positioned relative to?
Name three reasons position: sticky might not work.
Why do component libraries render dropdown menus in a portal?
Ask Aria about Positioning — relative, absolute, fixed and sticky
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.