What is CSS position: fixed?
position: fixed takes an element out of the normal document flow and “pins” it so it stays in the same place while the page scrolls.
Most of the time, that “place” is relative to the viewport (the visible browser window).
Think of fixed positioning as a little UI sticker: a chat bubble, a cookie bar, a floating “Back to top” button, a toolbar… it stays put while everything else scrolls behind it.
- Removed from flow: it won’t take up space, so content can slide underneath unless you leave room.
-
Offset properties apply: use
top,right,bottom,left(orinset) to place it. -
It’s not “magically on top”: stacking still depends on
z-indexand stacking contexts.
CSS position fixed basics (pinning to the viewport)
Let’s pin a little badge to different corners of the screen. Click the snippets to switch positions.
.badge {
position: absolute;
top: 14px;
left: 14px;
}
.badge {
position: fixed;
top: 14px;
left: 14px;
}
.badge {
position: fixed;
top: 14px;
right: 14px;
}
.badge {
position: fixed;
bottom: 14px;
right: 14px;
}
.badge {
position: fixed;
inset: auto auto 14px 14px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.scroller {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
}
.scroller p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.badge {
padding: 10px 12px;
border-radius: 999px;
border: 3px solid #111;
background: #ffd166;
font-weight: 700;
box-shadow: 0 10px 0 #111;
z-index: 99;
}
.badge span {
display: inline-block;
margin-left: 6px;
padding: 3px 8px;
border-radius: 999px;
background: #111;
color: #fff;
font-weight: 700;
font-size: 12px;
}
Scroll the page. The badge should stay pinned in the viewport. Try different snippets to move it around.
Fixed UI pinned
Notice how the badge stays in the same spot even while the page scrolls. That’s the hallmark of fixed positioning.
Using inset with fixed
inset is a shorthand for top, right, bottom, left.
It’s nice for fixed elements because you can describe the placement in one line.
-
inset: 0;pins the element to all sides (full-screen overlay). -
inset: auto 16px 16px auto;means “bottom-right with 16px offsets”.
CSS position fixed vs absolute
position: absolute and position: fixed are cousins: both are taken out of the normal flow and positioned with offsets.
The big difference is what they’re positioned against.
-
Absolute: positioned relative to its nearest positioned ancestor (the closest parent with
positionnotstatic), otherwise the initial containing block. - Fixed: positioned relative to the viewport most of the time, so it doesn’t move when the page scrolls.
Let’s compare them side-by-side. Scroll and watch what moves.
.floater {
position: absolute;
top: 14px;
right: 14px;
}
.floater {
position: fixed;
top: 14px;
right: 14px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
display: grid;
gap: 12px;
}
.panel {
position: relative;
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
}
.panel h4 {
margin: 0 0 10px 0;
font-size: 18px;
}
.panel p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.floater {
padding: 10px 12px;
border-radius: 14px;
border: 3px solid #111;
background: #06d6a0;
font-weight: 800;
box-shadow: 0 10px 0 #111;
z-index: 10;
}
.floater small {
display: block;
font-weight: 700;
opacity: 0.9;
}
Absolute vs Fixed
Click a snippet. Then scroll the page. Absolute moves with its container. Fixed stays pinned in the viewport.
Floater watch me while you scroll
With absolute, the floater is tied to the panel, so it scrolls away. With fixed, it stays in the same viewport corner even as the panel moves.
Learn more about position: absolute in the CSS Position Absolute Interactive Tutorial.
CSS position fixed vs sticky
position: sticky is like a “polite fixed” element: it behaves like normal content until it reaches a threshold (like top: 0),
then it sticks within its scroll container.
- Fixed: pinned (usually to the viewport) from the start, independent of scroll position.
- Sticky: scrolls normally, then sticks, and stops sticking when it reaches the end of its container.
.bar {
position: sticky;
top: 12px;
}
.bar {
position: fixed;
top: 12px;
left: 50%;
transform: translateX(-50%);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.shell {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
}
.shell p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.section {
margin-top: 12px;
border-radius: 14px;
border: 2px dashed rgba(0, 0, 0, 0.35);
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.06), rgba(0, 0, 0, 0) 45%),
url("https://picsum.photos/1000/620") center / cover no-repeat;
height: 900px;
}
.bar {
padding: 10px 14px;
border: 3px solid #111;
border-radius: 999px;
background: #ffd166;
font-weight: 900;
box-shadow: 0 10px 0 #111;
width: fit-content;
z-index: 20;
}
.hint {
font-size: 14px;
opacity: 0.9;
}
Fixed is always pinned. Sticky scrolls until it reaches
top: 12px, then sticks. Scroll and compare.
The sticky bar will stop being sticky when its container ends. Fixed doesn’t care about the container. It's fixed relative to the viewport.
Learn more about position: sticky; in the CSS Position Sticky Interactive Tutorial.
CSS position fixed center (middle of screen)
Centering a fixed element is a classic. There are two main approaches:
-
Translate trick:
top: 50%+left: 50%thentransform: translate(-50%, -50%). -
Inset + margin: give the element a size, then use
inset: 0andmargin: auto(no transforms needed).
.modal {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.modal {
position: fixed;
inset: 0;
margin: auto;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.stage {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
min-height: 340px;
position: relative;
overflow: hidden;
}
.stage p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.backdrop {
height: 640px;
border-radius: 14px;
border: 2px dashed rgba(0, 0, 0, 0.35);
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0) 45%),
url("https://picsum.photos/1200/720") center / cover no-repeat;
}
.modal {
width: min(420px, calc(100% - 36px));
height: 200px;
padding: 14px;
border: 3px solid #111;
border-radius: 18px;
background: #06d6a0;
box-shadow: 0 12px 0 #111;
z-index: 30;
display: grid;
gap: 8px;
align-content: start;
}
.modal h4 {
margin: 0;
font-size: 18px;
}
.modal .meta {
font-size: 14px;
opacity: 0.9;
}
.note {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
background: #111;
color: #fff;
font-size: 12px;
font-weight: 800;
width: fit-content;
}
This is a “modal-ish” box centered in the viewport. Switch snippets to change the centering technique.
Centered fixed box
The translate method is the most flexible, especially if your element doesn’t have a fixed size. The inset + margin method is wonderfully simple when the element has known width/height (or at least one of them).
Learn more about centering with CSS in the Centering With CSS Interactive Tutorial.
CSS position fixed not relative to viewport (surprise!)
Most of the time, fixed positioning is relative to the viewport. But there’s a big “gotcha”: if an ancestor creates a special containing block, the fixed element can become fixed relative to that ancestor instead.
The most common trigger is when a parent has transform (even transform: translateZ(0)),
but other properties can do it too (like filter and perspective).
In the playground below, we’ll put the fixed badge inside a card. If the card has a transform, the badge may behave like it’s “fixed inside the card” (it moves when the card scrolls).
.card {
transform: none;
}
.card {
transform: translateZ(0);
}
.card {
filter: blur(0);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.card {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
position: relative;
}
.card p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.tall {
height: 170px;
border-radius: 14px;
border: 2px dashed rgba(0, 0, 0, 0.35);
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.06), rgba(0, 0, 0, 0) 45%),
url("https://picsum.photos/1100/700") center / cover no-repeat;
}
.pin {
position: fixed;
bottom: 14px;
right: 14px;
padding: 10px 12px;
border: 3px solid #111;
border-radius: 14px;
background: #ffd166;
font-weight: 900;
box-shadow: 0 10px 0 #111;
z-index: 20;
}
.pin small {
display: block;
font-weight: 700;
opacity: 0.9;
}
Switch snippets and scroll. With some ancestor properties (like
transform), a fixed element can stop being “viewport-fixed”.Fixed badge watch what happens
Why does transform (and friends) affect fixed?
A transformed element creates a new containing block for some positioned descendants. In practice, that means your “fixed” element can become fixed to that transformed ancestor instead of the viewport.
- This commonly shows up in app layouts with animated wrappers, sliders, or GPU “performance hacks”.
-
If your fixed header/button suddenly scrolls with a container, look for
transformon parents.
CSS position fixed relative to parent (and “fixed within parent”)
There’s no official “position: fixed-in-parent” mode in CSS.
If you truly want something that stays inside a parent while scrolling, you usually want sticky, not fixed.
But there are a few practical patterns people mean when they say “fixed within parent”:
- Use sticky: it sticks inside the container and stops at the container’s end.
- Use a transformed ancestor on purpose: making fixed behave like it’s attached to a container (as shown above).
- Use an overlay/portal: move the element outside the container in the DOM so it can be truly viewport-fixed.
Here’s the “sticky inside parent” pattern (usually the best answer).
.sidebar {
position: sticky;
top: 14px;
}
.sidebar {
position: fixed;
top: 14px;
right: 14px;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.layout {
display: grid;
grid-template-columns: 1.5fr 0.9fr;
gap: 12px;
}
.box {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
}
.article {
min-height: 1100px;
border-radius: 14px;
border: 2px dashed rgba(0, 0, 0, 0.35);
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.06), rgba(0, 0, 0, 0) 45%),
url("https://picsum.photos/1200/760") center / cover no-repeat;
}
.sidebar {
padding: 12px;
border-radius: 14px;
border: 3px solid #111;
background: #06d6a0;
box-shadow: 0 10px 0 #111;
font-weight: 900;
}
.sidebar p {
margin: 8px 0 0 0;
font-weight: 700;
opacity: 0.9;
line-height: 1.4;
}
If your goal is “stay visible while this column scrolls, but don’t escape the column”, sticky is the right tool. If your goal is “always visible no matter what”, use fixed (but place it carefully so it doesn’t cover content).
CSS position fixed not working (common causes)
When fixed “doesn’t work”, it’s usually working… just not the way you expected. Here are the usual suspects.
1) You’re scrolling a container, not the page
If your layout uses a scroll container (like overflow: auto on a wrapper), your fixed element may appear weird depending on where it lives
and which ancestors create containing blocks.
A common fix is: place global fixed UI near the end of <body>, outside transformed/scrolling wrappers.
2) A parent has transform, filter, or perspective
As shown earlier, this can make fixed behave like it’s attached to that parent instead of the viewport.
Look up the ancestor chain in DevTools for transform (including “harmless” values).
3) It’s behind something (stacking contexts)
Fixed doesn’t automatically float above everything. If it’s hidden behind other elements:
-
Give it a
z-index(and ensure it’s not trapped in a lower stacking context). -
Watch out for parents that create stacking contexts (common ones:
transform,opacity < 1,filter,isolation).
Learn more in the CSS Z-Index Interactive Tutorial.
4) Mobile viewport quirks
On mobile browsers, the “viewport” can change as the browser UI appears/disappears (address bar). If your fixed footer/header feels jumpy, consider layout strategies that tolerate that movement (and test on real devices).
A quick debugging playground
This playground lets you toggle a few “problem makers”: a transformed wrapper and a low z-index.
If the fixed chip seems to “misbehave”, it’s usually one of these.
.app {
transform: none;
}
.chip {
z-index: 50;
}
.app {
transform: translateZ(0);
}
.chip {
z-index: 50;
}
.app {
transform: none;
}
.chip {
z-index: 0;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.app {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
}
.app p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.filler {
height: 90px;
border-radius: 14px;
border: 2px dashed rgba(0, 0, 0, 0.35);
background:
linear-gradient(180deg, rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0) 45%),
url("https://picsum.photos/1100/680") center / cover no-repeat;
}
.overlay {
position: relative;
margin-top: 12px;
border-radius: 14px;
border: 3px solid #111;
background: #111;
color: #fff;
padding: 10px 12px;
}
.chip {
position: fixed;
bottom: 14px;
left: 14px;
padding: 10px 12px;
border-radius: 999px;
border: 3px solid #111;
background: #ffd166;
font-weight: 900;
box-shadow: 0 10px 0 #111;
}
.chip small {
display: block;
font-weight: 700;
opacity: 0.9;
}
Toggle snippets, then scroll. Watch for two issues: (1) fixed behaving like it’s inside the wrapper (transform), (2) fixed appearing behind other stuff (z-index).
Fixed chip debug me
Practical fixed patterns you’ll use all the time
Floating action button (FAB)
A classic bottom-right button. We’ll add a slider to adjust its distance from the edges. Because we use a range slider, this playground has a single snippet.
.fab {
position: fixed;
inset: auto 14px 14px auto;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.card {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
}
.card p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.fab {
width: 56px;
height: 56px;
border-radius: 999px;
border: 3px solid #111;
background: #06d6a0;
box-shadow: 0 10px 0 #111;
display: grid;
place-items: center;
font-weight: 900;
z-index: 50;
}
.fab:active {
transform: translateY(2px);
box-shadow: 0 8px 0 #111;
}
A floating action button is usually
position: fixed. Drag the slider to change its offset.
Cookie bar (fixed to the bottom)
A fixed bar at the bottom is basically a rite of passage on the modern web. Just remember: fixed elements can cover content, so you might need padding on the page if the bar is persistent.
.cookie {
position: absolute;
left: 14px;
right: 14px;
bottom: 14px;
}
.cookie {
position: fixed;
left: 14px;
right: 14px;
bottom: 14px;
}
.cookie {
position: fixed;
inset: auto 0 0 0;
border-radius: 0;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.page {
border: 3px solid #111;
border-radius: 18px;
background: #fff;
padding: 14px;
box-shadow: 0 12px 0 #111;
}
.page p {
margin: 0 0 12px 0;
line-height: 1.5;
}
.cookie {
padding: 12px 14px;
border: 3px solid #111;
border-radius: 18px;
background: #ffd166;
box-shadow: 0 10px 0 #111;
z-index: 60;
display: grid;
gap: 6px;
}
.cookie strong {
font-size: 16px;
}
.cookie .meta {
font-size: 14px;
opacity: 0.9;
}
Scroll. The cookie bar stays pinned. Switch snippets to see a “floating” bar vs a full-width bottom bar.
Summary + mini cheat sheet
-
Use
position: fixedfor UI that should stay in one place while the page scrolls (toolbars, floating buttons, cookie bars). -
Use
position: absolutewhen you want positioning tied to a specific ancestor box. -
Use
position: stickywhen you want something to stick within a section/container and stop at the end. -
If fixed is “not relative to the viewport”, inspect ancestors for
transform,filter, orperspective. -
If fixed is “not working”, check
z-indexand stacking contexts, and confirm you’re not inside a transformed wrapper.
CSS Position Fixed Conclusion
position: fixed is a powerful tool for creating persistent UI elements that stay visible as users scroll.
By understanding how it interacts with the viewport, ancestors, and stacking contexts, you can use it effectively in your layouts.
Remember to test on real devices, especially for mobile viewport quirks, and consider user experience when overlaying content.
