The CSS position property, sometimes called CSS placement or CSS positioning, controls how an element is placed in the document and what it positions itself relative to. It is the foundation of overlays, dropdowns, sticky navigation, tooltips, and any UI pattern where elements need to sit on top of or independently from the normal page flow. This guide covers all five position values, static, relative, absolute, fixed, and sticky, plus the inherit, initial, and unset keywords, with practical examples throughout.
Short answer: static is the default and ignores offsets. relative shifts an element from its own spot and keeps its space. absolute removes it from the flow and places it against the nearest positioned ancestor. fixed places it against the viewport. sticky scrolls normally, then sticks at a threshold inside its parent.
How CSS position works
By default, every element sits in the normal document flow, stacked one after another based on the order it appears in the HTML. The position property lets you change this behaviour in different ways depending on the value you choose.
Once you give an element a position value other than static, you can use the offset properties top, right, bottom, and left to move it. These offsets mean different things for different position values, which is the part most developers need to understand clearly.
Quick reference
| Value | In document flow? | Positions relative to | Common use |
|---|---|---|---|
| static | Yes | N/A (offsets ignored) | Default, reset |
| relative | Yes | Its own normal position | Containing block, minor nudging |
| absolute | No | Nearest positioned ancestor | Tooltips, badges, overlays |
| fixed | No | Browser viewport | Sticky nav, modals, chat buttons |
| sticky | Yes | Scroll container + own position | Sticky headers, sidebar sections |
position: static
position: static is the default value of the CSS position property, so the default CSS position is static, and it needs no configuration. Every element uses it unless you or a stylesheet sets something else. A statically positioned element sits exactly where the normal document flow places it. The top, right, bottom, left, and z-index properties have no effect on static elements. This is why stacking order tricks only work once an element has a position value other than static.
/* Default: no need to write this explicitly */ .element { position: static; } /* Common use: resetting position on a child inside a positioned parent */ .reset-position { position: static; }
position: relative
position: relative keeps the element in the normal document flow. Its original space is preserved, meaning surrounding elements do not reflow. The offset properties move it visually from where it would naturally sit, but the space it occupied stays in place.
.nudged { position: relative; top: 8px; /* moves DOWN 8px from its natural position */ left: 12px; /* moves RIGHT 12px from its natural position */ }
What position: relative does and does not do
- It keeps the element in the flow and keeps its original space, so neighbouring elements never move.
- Offsets push it away from the named side:
topmoves it down,leftmoves it right,bottommoves it up,rightmoves it left. - With no offsets it looks exactly like
static, but it now acceptsz-indexand becomes the reference point for absolutely positioned children. - It does not change the element's size or the size of its parent.
The same value works as an inline style in plain HTML:
<div style="position: relative; top: 4px;">Nudged down 4px</div>
The most important use of position: relative is not moving an element: it is creating a containing block for absolutely positioned children. Set position: relative on a parent to make absolutely positioned descendants position themselves relative to that parent instead of the page.
.card { position: relative; /* becomes the containing block */ } .card .badge { position: absolute; top: 12px; right: 12px; /* 12px from the card's top-right corner */ }
position: absolute
position: absolute removes an element completely from the document flow. The surrounding elements reflow as if it does not exist. It is then placed relative to the nearest ancestor that has a position value other than static, an element's nearest positioned ancestor.
If no positioned ancestor exists, the element positions itself relative to the initial containing block, which is effectively the html element at the top of the page. In other words, an absolutely positioned element is placed relative to the document body unless it sits inside another positioned element.
.parent { position: relative; /* required: makes .parent the containing block */ width: 300px; height: 200px; } .child { position: absolute; top: 0; /* 0px from parent's top edge */ right: 0; /* 0px from parent's right edge */ width: 80px; height: 80px; }
Centering with position absolute
A classic use case for absolute positioning is centering an element precisely inside a parent when flexbox or grid is not available or appropriate:
.parent { position: relative; } .centered { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); /* translate(-50%, -50%) shifts the element back by half its own size */ }
Full-coverage overlay
.card { position: relative; overflow: hidden; } .card-overlay { position: absolute; inset: 0; /* shorthand for top: 0; right: 0; bottom: 0; left: 0 */ background: rgba(0, 0, 0, 0.5); opacity: 0; transition: opacity 0.3s ease; } .card:hover .card-overlay { opacity: 1; }
Remember: absolute positioning removes the element from the flow. If a parent's height depends on its children's content, absolutely positioned children will not contribute to that height. The parent may collapse to zero height if all its children are absolute.
position: fixed
position: fixed removes an element from the document flow and positions it relative to the browser viewport, not any parent element. It stays in the same screen position even as the user scrolls.
.sticky-nav { position: fixed; top: 0; left: 0; width: 100%; z-index: 100; background: #0f111a; } /* Floating action button: bottom right corner */ .fab { position: fixed; bottom: 24px; right: 24px; width: 56px; height: 56px; border-radius: 50%; z-index: 200; } /* Modal overlay: covers entire viewport */ .modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); z-index: 300; }
Fixed elements and transforms: if an ancestor element has a transform, filter, or will-change property applied, it creates a new containing block that overrides the viewport. Fixed elements inside a transformed ancestor no longer behave as viewport-fixed. This is a common source of sticky nav bugs.
Position fixed relative to a parent
Fixed ignores parents by design. The exception is an ancestor with transform, filter, backdrop-filter, perspective, or contain: layout or contain: paint. That ancestor becomes the containing block, so the fixed child is measured from it and scrolls with it. This is usually an accident, but it can be used on purpose:
/* Usual answer: absolute inside a relative parent */ .panel { position: relative; } .panel .pin { position: absolute; top: 0; right: 0; } /* Deliberate trick: the parent becomes the containing block for fixed children */ .panel { transform: translateZ(0); } .panel .pin { position: fixed; top: 0; right: 0; }
position: sticky
position: sticky is a hybrid of relative and fixed. The element behaves like relative as the user scrolls down the page. When it reaches the offset threshold you define, it sticks in place and behaves like fixed. When its parent container scrolls off screen, the sticky element goes with it.
.sticky-header { position: sticky; top: 0; /* sticks when it reaches the top of the viewport */ background: #0f111a; z-index: 10; } /* Sticky table header */ thead th { position: sticky; top: 0; background: #1a1d27; } /* Sidebar that sticks while scrolling page content */ .sidebar { position: sticky; top: 80px; /* accounts for a fixed navbar height */ align-self: flex-start; /* required in flex layouts */ }
Why sticky is not working
There are three common reasons position: sticky fails silently:
/* Problem 1: no offset value set */ .header { position: sticky; /* missing top, bottom, left, or right: sticky will not activate */ } /* Problem 2: parent has overflow: hidden or overflow: auto */ .parent { overflow: hidden; /* this kills sticky on children */ /* overflow: clip hides overflow without making a scroll container, so sticky keeps working */ } /* Problem 3: parent is not tall enough */ /* sticky only works while the parent is in view. if the parent is the same height as the sticky element, there is no room to scroll within it */
Quick fix checklist for sticky: confirm top is set, check that no ancestor has overflow: hidden or overflow: auto, and make sure the parent is significantly taller than the sticky element.
position: inherit, initial, and unset
Beyond the five position values above, CSS provides three global keywords that work on any property, including position. They show up rarely in day-to-day work, but they matter in resets and when overriding styles you do not fully control.
.child { position: inherit; /* copies the computed position value from the parent element */ } .reset { position: initial; /* resets to the CSS specification default, which is static */ } .reset-cascade { position: unset; /* falls back to inherit for inherited properties, or initial otherwise */ }
position is not an inherited property by default, so a child element computes to static regardless of its parent unless you explicitly write position: inherit. Because position is not inherited, unset behaves the same as initial here, both resolve to static. You will run into these keywords most often inside CSS reset stylesheets or when neutralizing a third-party component's positioning without knowing its exact value.
The offset properties: top, right, bottom, left
The four offset properties work differently depending on the position value:
| Property | What it does |
|---|---|
| top | Distance from the top edge of the containing block (or viewport for fixed) |
| right | Distance from the right edge |
| bottom | Distance from the bottom edge |
| left | Distance from the left edge |
| inset | Shorthand for all four: inset: 0 sets all to 0 at once |
For relative, positive top pushes the element down from its normal position and positive left pushes it right. For absolute and fixed, the values define the gap between the element's edge and the containing block's edge.
/* relative: offset from natural position */ .icon { position: relative; top: -2px; /* nudge up 2px to align with text baseline */ } /* absolute: gap from the containing block edges */ .tooltip { position: absolute; top: 100%; /* just below the parent's bottom edge */ left: 0; margin-top: 8px; }
Position not working? Quick checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| top, left and other offsets do nothing | The element is static | Set position to anything other than static |
| Absolute element jumps to the page corner | No positioned ancestor | Add position: relative to the intended parent |
| z-index is ignored | The element is static (flex and grid items are the exception) | Give it a position value, or see the z-index guide |
| Sticky never sticks | No offset, an overflow ancestor, or a parent no taller than the element | Set top, use overflow: clip instead of hidden, make the parent taller |
| Fixed element scrolls with its container | An ancestor has transform, filter or similar | Remove that property or move the element outside the ancestor |
| Parent collapses to zero height | All its children are absolute | Give the parent a height or min-height |
Practical patterns
Notification badge on an icon
.icon-wrapper { position: relative; display: inline-flex; } .badge { position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; background: #ef4444; color: #fff; font-size: 11px; font-weight: 700; border-radius: 9px; display: flex; align-items: center; justify-content: center; }
Sticky navigation with page content offset
.nav { position: sticky; top: 0; height: 64px; z-index: 100; } /* Push page content down so it is not hidden under the nav */ .page-content { padding-top: 64px; } /* Alternative: use fixed nav and add body padding */ .nav-fixed { position: fixed; top: 0; left: 0; width: 100%; height: 64px; z-index: 100; } body { padding-top: 64px; }
Fixed headers and anchor links
A fixed or sticky header covers whatever an anchor link scrolls to, and keyboard focus can land underneath it too. WCAG 2.2 criterion 2.4.11, Focus Not Obscured (Minimum), exists for this case. Reserve the header's height with scroll padding:
html { scroll-padding-top: 72px; /* header height plus a little breathing room */ }
Sticky footer
"Sticky footer" searches usually mean one of two different things, so it helps to know which one you actually need.
/* Meaning 1: a footer pinned to the bottom of the viewport while scrolling */ .footer-pinned { position: fixed; bottom: 0; left: 0; width: 100%; } /* Meaning 2: the classic "sticky footer" layout, where the footer sits at the bottom of a short page but scrolls normally on a long one. This is a flexbox technique. It does not use position at all */ body { display: flex; flex-direction: column; min-height: 100vh; } main { flex: 1; /* pushes the footer down when content is short */ }
Most "sticky footer" tutorials describe the second technique, a page-length layout fix, not position: sticky. If your footer needs to stay visible while the user scrolls through long content, use the fixed version instead. On mobile, see the dvh, svh and lvh guide for why 100vh misbehaves.
Tooltip above a button
.btn-wrapper { position: relative; display: inline-block; } .tooltip { position: absolute; bottom: calc(100% + 8px); /* 8px above the button */ left: 50%; transform: translateX(-50%); white-space: nowrap; background: #1e2235; color: #e2e8f0; padding: 6px 12px; border-radius: 6px; font-size: 13px; opacity: 0; transition: opacity 0.2s ease; } .btn-wrapper:hover .tooltip { opacity: 1; }
This works until the tooltip hits a viewport edge, because absolute positioning cannot flip itself. CSS anchor positioning handles that case, and you can try it in the CSS Anchor Positioning Playground. Check browser support before relying on it.
Full-screen modal dialog
.modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.6); display: flex; align-items: center; justify-content: center; z-index: 400; } .modal-dialog { position: relative; /* for close button positioning */ background: #1a1d27; border-radius: 12px; padding: 32px; max-width: 480px; width: 100%; } .modal-close { position: absolute; top: 16px; right: 16px; }
Native alternative: the HTML <dialog> element opened with showModal() renders in the browser's top layer, so you get a backdrop and correct stacking without a fixed overlay or a z-index race.
Position utilities in Tailwind CSS
If you're working in Tailwind CSS, the position utility class names match the CSS values directly, so there is no separate naming convention to learn.
<!-- the five position values map straight to class names --> <div class="static">...</div> <div class="relative">...</div> <div class="absolute">...</div> <div class="fixed">...</div> <div class="sticky">...</div> <!-- offsets pair a side with a value from the spacing scale --> <div class="absolute top-0 right-4">...</div> <!-- inset shorthand utilities --> <div class="absolute inset-0">...</div> /* top, right, bottom, left all 0 */ <div class="absolute inset-x-0">...</div> /* left and right only */ <div class="absolute inset-y-4">...</div> /* top and bottom only */ <!-- badge pattern, same idea as the plain CSS version above --> <div class="relative"> <span class="absolute -top-1 -right-1 h-4 w-4 rounded-full bg-red-500"></span> </div>
The rule that matters most carries over unchanged from plain CSS: an absolute element still looks for the nearest ancestor that is not static, so pair it with relative on the parent. Breakpoint prefixes work here too. For example, md:absolute only applies the absolute position starting at the medium breakpoint. To convert other CSS into utilities, use the CSS to Tailwind converter.
FAQ
What is the difference between position absolute and position fixed?
position: absolute is placed relative to its nearest positioned ancestor. If you scroll the page, an absolutely positioned element scrolls with the content. position: fixed is always placed relative to the browser viewport and stays in the same spot even when the user scrolls. Use absolute for tooltips, dropdowns, and overlays inside a container. Use fixed for sticky navigation bars and persistent UI elements.
What does position: relative do?
position: relative keeps an element in the normal flow and lets you shift it from its natural spot with top, right, bottom, and left. The space it originally occupied stays reserved, so neighbouring elements do not move. It also makes the element the containing block for absolutely positioned children, which is its most common use.
What is the difference between position static and position relative?
Both keep the element in normal flow and look identical until you add an offset. position: static ignores top, right, bottom, left, and z-index. position: relative honours them, and also becomes the reference point for absolutely positioned descendants.
Why is my position absolute element not positioning correctly?
The most common cause is a missing positioned ancestor. An absolutely positioned element looks for the nearest ancestor with position set to relative, absolute, fixed, or sticky. If none exists, it positions itself relative to the root html element. Add position: relative to the parent you want to position against.
What is the nearest positioned ancestor in CSS?
The nearest positioned ancestor is the closest parent element in the DOM tree whose position value is anything other than static, meaning relative, absolute, fixed, or sticky. An absolutely or fixed positioned element uses this ancestor as its containing block, so its top, right, bottom, and left offsets are measured from that ancestor's edges instead of the page. If no ancestor qualifies, the element falls back to the initial containing block, which is the root html element.
Does position absolute remove an element from the document flow?
Yes. Both position: absolute and position: fixed remove an element from normal document flow entirely. The element no longer occupies space in the layout and surrounding elements reflow as if it does not exist. position: relative and position: sticky keep the element in the normal flow.
How does position sticky work?
A sticky element behaves like position: relative until it hits a scroll threshold defined by top, bottom, left, or right. At that point it sticks in place, but only within its parent container. When the parent scrolls out of view, the sticky element goes with it. You must set at least one offset property (usually top) or the element will not stick.
What is the difference between position relative and position absolute?
position: relative keeps the element in normal document flow but lets you offset it from its natural position. The original space is preserved. position: absolute removes the element from the flow entirely and positions it relative to the nearest positioned ancestor. No space is reserved for it in the layout.
When should I use position fixed vs position sticky?
Use position: fixed when the element must always be visible regardless of scroll position and parent container, such as a chat widget or cookie banner. Use position: sticky when the element should scroll normally until it reaches a threshold, then stick only within its parent, such as a sticky table header or a sidebar that stops at the footer.
Can position: fixed or position: sticky be positioned relative to a parent instead of the viewport?
Not directly for fixed, and only partially for sticky. position: fixed always calculates its offsets against the browser viewport, even when nested deep inside other elements, with one exception: if an ancestor has a transform, filter, backdrop-filter, perspective, or a contain value of layout or paint, that ancestor becomes the fixed element's containing block instead of the viewport. position: sticky is already scoped to its nearest scrolling parent by design, and only escapes that parent once the parent itself scrolls out of view. If you want an element pinned within one specific container rather than the whole page, position: absolute on a position: relative parent is usually the right tool instead of fixed.
What is the default value of the CSS position property?
The default value is static. Every element uses position: static unless you or a stylesheet sets something else. You can also restore an element to this default explicitly with position: initial or position: unset, since position is not an inherited property. Setting position: inherit instead copies whatever computed position value the parent element has, which is different from resetting to the specification default.
Is the position property the same as background-position or object-position?
No, they are different properties that happen to share the word position. position, covered in this guide, controls where an element sits in the page layout using static, relative, absolute, fixed, or sticky. background-position controls where a background image sits inside its own element's box, using values like center or top right. object-position does the same job for the content of a replaced element such as img or video when combined with object-fit. All three accept offset-style values, which is why they get confused, but changing one has no effect on the other two.
Place tooltips and popovers visually
Drag an anchor element in the CSS Anchor Positioning Playground, preview automatic fallbacks, and copy the CSS. It picks up where position: absolute stops being enough.
Open Anchor Positioning Playground →