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, the inherit, initial, and unset keywords, and the rules that govern each one, with practical examples throughout.
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
static is the default value. Every element has it unless you explicitly change it. 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
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 */ }
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
An absolutely positioned element is completely removed 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.
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.
.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
A fixed element is removed from the document flow and positioned 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: sticky
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 */ } /* 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; }
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; }
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.
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; }
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; }
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.
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.
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.
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 scoped 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, perspective, or will-change property set, 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.
Generate CSS animations with transforms
Our CSS Animation Generator lets you build keyframe animations using translate, rotate, and scale. These properties work alongside position for powerful UI effects.
Open Animation Generator →