Blog
3 min read

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

The five CSS position values and what each does: static (normal flow), relative (nudge and anchor), absolute (place inside the nearest positioned ancestor), fixed (stick to the screen) and sticky (stick while scrolling). With examples and the reasons sticky and absolute 'don't work'.

The CSS position property decides how an element is placed on the page. There are five values, and most layout confusion comes from mixing up relative and absolute.

static — the default

.box { position: static; }

The element sits in the normal flow of the page: one after another, top to bottom. top, left, right, bottom and z-index have no effect. You almost never write static explicitly.

relative — nudge it, and make it an anchor

.box { position: relative; top: 10px; left: 20px; }

The element stays in the normal flow (its space is kept), but you can nudge it from where it would have been. Moving things this way is rare.

The real reason people use position: relative is the second effect: it becomes the reference point for absolutely positioned children.

absolute — place it inside the nearest positioned ancestor

.card { position: relative; }
.badge { position: absolute; top: 8px; right: 8px; }
<div class="card">
  <span class="badge">New</span>
  ...
</div>

An absolutely positioned element:

  • is removed from the normal flow — other elements act as if it isn't there,
  • is placed relative to its nearest ancestor that has a position other than static.

That's why the pattern is always "parent relative, child absolute". If no ancestor is positioned, the element is placed relative to the whole page — which is why a badge sometimes flies to the top corner of the screen.

fixed — stuck to the screen

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

Placed relative to the browser window, and stays there when you scroll. Used for floating buttons, cookie banners and some headers.

Gotcha: if any ancestor has a transform, filter or perspective set, fixed behaves like absolute relative to that ancestor instead. If your fixed element scrolls away, check the ancestors.

sticky — normal until you scroll past it

.table-header { position: sticky; top: 0; }

Behaves like relative until scrolling would take it off-screen, then sticks at the top value — until its parent container scrolls away. Perfect for table headers, section headings and sidebars.

"Sticky isn't working"

The usual causes:

  1. No top (or bottom) value. Sticky needs to know where to stick.
  2. An ancestor has overflow: hidden, auto or scroll. Sticky sticks within the nearest scrolling ancestor; overflow: hidden on a parent breaks it.
  3. The parent is only as tall as the sticky element, so there's no room to stick.
  4. It's inside a flex/grid container stretching it — add align-self: start.

Quick reference

Value In normal flow? Positioned relative to Typical use
static Yes — Default
relative Yes Its own normal spot Anchor for absolute children
absolute No Nearest positioned ancestor Badges, dropdowns, overlays
fixed No The viewport Floating buttons, banners
sticky Yes Scroll container Sticky headers

Layering: z-index

Positioned elements can overlap. z-index controls which is on top — but only works on positioned elements and within "stacking contexts". If z-index seems to be ignored, see z-index not working.

Do you need position at all?

For page layout — columns, centring, spacing — use Flexbox or Grid, not position. Positioning is for elements that need to break out of the layout: overlays, badges, sticky bits. (Flexbox vs Grid, How to center a div)


EasySpawn runs your app on a server with a live preview, and Claude Code can fix a misbehaving layout while you watch the page update. See how it works or join the waitlist.

Related: z-index Not Working? · Flexbox vs Grid · How to Center a Div · Responsive Design Basics

Keep reading