aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

CSS position: relative, absolute, sticky and fixed

The CSS position property is the core tool that decides how an element is placed on the page and whether offsets like top, right, bottom, left (and inset) apply to it at all. Most of the confusion comes from not knowing exactly how the five values (static, relative, absolute, fixed, sticky) differ from one another. In this article I clarify each one with real examples and working snippets.

position: static — the default

Every element ships with position: static unless you say otherwise. Static elements sit in the normal flow: block elements stack vertically, inline elements run side by side. The key detail: offset properties like top and left have no effect on a static element. Neither does z-index.

.box {
  position: static; /* default */
  top: 50px; /* no effect at all */
}

So if you want to nudge an element, the first step is to take it out of static.

position: relative — shifting relative to itself

relative keeps the element in the normal flow but lets you apply offsets. Those offsets are calculated relative to the element's own original position. Importantly, even when the element shifts, its original slot in the flow is preserved — neighbouring elements behave as if it were still there, and no gap is left behind.

.badge {
  position: relative;
  top: -8px;
  left: 12px;
}

The second, equally common use of relative is an invisible one: it creates a positioning context for the absolute elements inside it. Very often you give a box position: relative with no offsets at all; your only goal is to anchor its children to it.

position: absolute — out of the flow

absolute removes the element from the normal flow entirely; it no longer takes up space for its neighbours. Its position is calculated from the nearest positioned ancestor (the closest parent whose position is not static). If there is no such ancestor, the reference falls back to the initial containing block (usually the viewport / <html>).

The classic pattern is: relative on the outside, absolute on the inside.

.card {
  position: relative;
}
.card .tag {
  position: absolute;
  top: 8px;
  right: 8px;
}

Here .tag sticks to the top-right corner of the card. Without relative on .card, the tag would fly off to the nearest positioned ancestor on the page. That's why the answer to "my absolute element is in the wrong place" is almost always: you forgot to set relative on the right ancestor.

  • If you set both top and bottom without a height, the element stretches vertically.
  • The inset: 0 shorthand sets all four edges to 0, stretching the element to cover its ancestor completely.

position: fixed — pinned to the viewport

fixed also takes the element out of the flow, but its reference is always the viewport (not an ancestor). The element stays put on screen even as the page scrolls. It's ideal for fixed navigation bars, "back to top" buttons and cookie banners.

.feedback {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 1000;
}

One critical exception: if an ancestor has a property like transform, filter or perspective, that ancestor creates a new containing block, and the fixed element becomes fixed relative to that ancestor instead of the viewport. If you have a fixed element drifting unexpectedly, check whether a parent has a transform.

position: sticky — between relative and fixed

sticky is a hybrid of the two. The element first sits in the normal flow like relative; as you scroll, once it reaches the threshold you set (e.g. top: 0) it sticks in place like fixed. When you reach the end of its container, it is released again.

.section-heading {
  position: sticky;
  top: 0;
  background: #fff;
}

When sticky doesn't work, there are three usual culprits:

  • Missing threshold: you must provide at least one of top, bottom, left or right; without it, nothing sticks.
  • overflow on an ancestor: if a parent has overflow: hidden/scroll/auto, the sticky element binds to that container's scroll context and won't behave as you expect.
  • Not enough room: a sticky element only stays stuck for the length of its own container; if the container is short, the effect lasts only briefly.

z-index and stacking contexts

z-index only works on positioned elements (any value other than static) and on flex/grid children. A higher z-index brings an element to the front — but only within the same stacking context. Properties like transform, opacity (below 1) and filter create a new stacking context, which is why sometimes even z-index: 9999 can't lift an element up: it's trapped inside a lower-priority context.

Which value, and when?

  • Just need a small nudge while keeping the flow? → relative
  • Placing one element in the corner of another? → relative on the parent, absolute on the child
  • Should it stay fixed on screen regardless of scroll? → fixed
  • Should it stick while scrolling but release at the section's end? → sticky

Frequently Asked Questions

What's the difference between absolute and fixed?

Both remove the element from the normal flow. absolute is positioned relative to the nearest positioned ancestor and scrolls with the page; fixed is positioned relative to the viewport and stays put on screen even as the page scrolls.

Why won't my sticky element stick at all?

The two most common reasons: you didn't provide a threshold value (like top/bottom), or a parent has overflow: hidden or overflow: auto. The latter silently disables sticky.

Why isn't my z-index working?

Usually because the element is static (give it a position value first) or because it's trapped inside a lower-priority stacking context. Check ancestors for transform, opacity or filter.

Is your UI breaking because of positioning? We can sort out sticky headers, dropdown overlaps and layering issues together. If you have a project, get in touch with me.

Bu kategorideki tüm yazılar →

Devamı için