CSS Position Explained: static, relative, absolute, fixed and sticky

CSS Position Explained: static, relative, absolute, fixed and sticky

The position property decides how an element is placed on the page: where it sits, what it's measured from, and whether it moves when you scroll. It's behind sticky headers, notification badges, dropdown menus, popups and chat buttons.

There are five values. This guide shows each one with a picture, explains the rule behind it, and gives you real examples to copy.

Open the position playground Switch values and scroll a real page to see the difference.

The short version

ValuePlaced fromTakes up space?Moves when you scroll?
staticNormal page flowYesYes
relativeIts own normal spotYes (its original space)Yes
absoluteNearest positioned ancestorNoYes, with that ancestor
fixedThe browser windowNoNo
stickyNormal flow, then its scroll containerYesUntil it reaches its offset

First: top, right, bottom, left

Positioned elements are moved with four offset properties: top, right, bottom and left. Each one is a distance from an edge. top: 20px means “20px down from the top”, and right: 0 means “touching the right edge”.

The shorthand inset sets all four at once: inset: 0 is the same as top: 0; right: 0; bottom: 0; left: 0. What the offsets are measured from depends on the position value, which is what the rest of this guide is about.

1. static: the default

Every element starts as position: static. It sits wherever it falls in the normal flow of the page, one block after another. Offsets and z-index are ignored. You'll rarely write static yourself, except to undo another value.

2. relative: nudged from its normal spot

Left: a static box ignores top and left; right: a relative box moved 20px down and 40px right, with a dashed outline showing its original spot and the text below not moving
A relative box moves, but its original space is kept.
CSS: relative
.box {
  position: relative;
  top: 20px;    /* 20px down from where it would be */
  left: 40px;   /* 40px right from where it would be */
}

A relative element is laid out normally first, then shifted by its offsets. The important part: the space it originally took stays reserved. Nothing around it moves, so it may overlap its neighbours.

In practice, position: relative is mostly used with no offsets at all, as an anchor for absolutely positioned children. That's the next value.

3. absolute: placed inside a positioned parent

Left: a NEW badge on the corner of a card that has position relative; right: the same badge flies to the corner of the page because the card has no position
The badge goes to the nearest positioned ancestor, or to the page if there is none.
CSS: a badge on a card
.card {
  position: relative;   /* the anchor */
}
.badge {
  position: absolute;
  top: -10px;
  right: -10px;         /* hang just over the top-right corner */
}

An absolute element is removed from the normal flow. The other elements close the gap as if it weren't there. It's then placed using its offsets, measured from its containing block: the nearest ancestor whose position is anything other than static.

If no ancestor is positioned, it's placed against the page itself. That's the #1 absolute positioning bug: the badge ends up in the corner of the screen instead of the corner of the card. The fix is almost always position: relative on the parent.

Some other things to know:

CSS: dark overlay covering an image
.photo { position: relative; }
.photo::after {
  content: "";
  position: absolute;
  inset: 0;                         /* cover the whole parent */
  background: rgba(0, 0, 0, .4);
}

4. fixed: pinned to the window

Two browser windows, before and after scrolling: the page text has moved up, but the fixed header at the top and the chat button at the bottom right are in the same place
Fixed elements stay in the same place on screen while the page scrolls.
CSS: fixed header and chat button
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
}
body {
  padding-top: 64px;   /* the header's height, so it doesn't cover the content */
}

.chat-button {
  position: fixed;
  bottom: 20px;
  right: 20px;
}

A fixed element is placed relative to the browser window (the viewport) and doesn't move when the page scrolls. Like absolute, it leaves the normal flow, so the content slides underneath it. That's why the example adds padding to the body: otherwise the top of the page hides behind the header.

Watch out: if any ancestor has a transform, filter or perspective, the fixed element is placed relative to that ancestor instead of the window, and it scrolls with it. If a fixed element suddenly scrolls, look for a transform higher up.

5. sticky: normal, then stuck

Three scroll positions: section headings sit in the page normally, then Section A sticks to the top as the page scrolls, then Section B replaces it when Section A ends
Sticky elements scroll normally until they reach their offset, then stick.
CSS: sticky section headings
.section h2 {
  position: sticky;
  top: 0;              /* stick when it reaches the top */
  background: #fff;    /* so the text scrolling under it is hidden */
}

Sticky is a mix of relative and fixed. The element behaves normally until scrolling brings it to its offset (here top: 0), then it sticks there. It stays stuck only while its parent is still on screen. When the parent scrolls away, the sticky element goes with it.

That's what makes the “one heading replaces another” effect in the picture: each heading is inside its own section.

Sticky not working? Check these three things:

  1. You set an offset. position: sticky does nothing without top (or bottom, left, right).
  2. The parent is taller than the element. If the parent is the same height, there's no room to stick.
  3. No ancestor has overflow: hidden, auto or scroll. That ancestor becomes the scroll container, and the element sticks inside it instead of the page.

z-index: which element is on top?

Three overlapping cards with z-index 1, 3 and 2; the card with z-index 3 is in front of the others
The highest z-index is closest to you.

When positioned elements overlap, z-index decides the order front to back. Higher numbers come forward. With equal values, or no z-index, the element that comes later in the HTML is on top.

CSS: dropdown above the page
.menu {
  position: relative;
}
.menu .dropdown {
  position: absolute;
  top: 100%;    /* just below the menu button */
  left: 0;
  z-index: 10;  /* above the content that follows */
}

Two rules cause most z-index confusion:

Tip: use small, planned numbers, for example 10 for dropdowns, 100 for the header and 1000 for modals, instead of an arms race of 99999s.

Which position should I use?

I want to…Use
Put a badge or icon on the corner of a cardabsolute inside a relative parent
Cover an image with a dark overlay or captionabsolute + inset: 0
Open a dropdown under a buttonabsolute, top: 100%
Keep a header visible while scrollingsticky (or fixed)
Show a chat or “back to top” buttonfixed
Make a popup cover the whole screenfixed + inset: 0
Nudge an icon down by 2pxrelative + top: 2px
Lay out a page or a row of itemsnone: use Flexbox or Grid

Position is for special placement. For general page layout, use Flexbox or Grid. Layouts built with absolute positioning break as soon as the content or screen size changes.

Common mistakes

Try it in the playground

The CSS position playground: position value buttons and top and left sliders on the left, a scrollable page preview with a relative box offset from its dashed original spot, and the generated CSS
Pick a value, move the box, then scroll the preview.

The position playground shows a real, scrollable page with a box inside a parent. Try these experiments:

  1. Choose relative and move the sliders. The dashed outline shows the space it still keeps.
  2. Choose absolute, then untick “parent has position: relative”. The box jumps to the corner of the page.
  3. Choose fixed and scroll the preview. The box stays put.
  4. Choose sticky and scroll slowly. The box sticks, then leaves with its parent.

Share your work

Built a sticky header or a badge? Share a screenshot as a link with sshot.online, or share your code with the free code sharing tool. No account needed.

Frequently asked questions

What is the difference between position relative and absolute?

A relative element stays in the page flow and is shifted from its normal spot, keeping its original space. An absolute element is taken out of the flow and placed inside its nearest positioned ancestor, so other elements close the gap.

Why is my absolute element positioned relative to the page?

None of its ancestors has a position other than static, so it uses the page instead. Add position: relative to the parent you want it placed inside.

What is the difference between fixed and sticky?

A fixed element is always pinned to the browser window and leaves the page flow. A sticky element stays in the flow, scrolls normally, and only sticks once it reaches its offset, and only while its parent is visible.

Why is position sticky not working?

Usually the element has no top value, its parent is not taller than it, or an ancestor has overflow set to hidden, auto or scroll.

Why does z-index not work?

z-index has no effect on position: static elements, except flex and grid items. It can also be limited by a parent that creates a stacking context, for example with its own z-index, opacity or transform.

What does inset: 0 mean?

inset is shorthand for top, right, bottom and left. inset: 0 sets all four to 0, which stretches an absolute or fixed element to cover its containing block.

Related articles

← All guides