Himanshu SharmaSoftware engineer
Let’s talk

CSS · Lesson 19 of 29

Positioning

Move elements out of normal flow with CSS position, using relative, absolute, fixed and sticky, plus top, left, z-index and stacking contexts.

14 minLayoutUpdated 18 October 2026

By the end of this lessonPlace badges, overlays, sticky headers and fixed buttons correctly, and explain what each position value is relative to.

Normal flow and when to leave it

Browsers lay boxes out in normal flow: blocks stack down the page, inline items run along lines. That is position: static, the default for every element, and it is what you want most of the time. Positioning lets you move a box out of that flow for special jobs: a badge on a corner of a card, a tooltip, a header that stays on screen, a chat button floating in the corner.

For layout of the page itself, use Flexbox and Grid. Use position for the exceptions.

The offsets you use with it are top, right, bottom and left, and inset as a shorthand for all four.

relative

position: relative leaves the element in normal flow (its original space is kept) but lets you shift it:

html
<style>
  .a, .b { padding: 10px; background: #bfdbfe; margin: 6px 0; }
  .b { position: relative; top: 14px; left: 30px; background: #fbcfe8; }
</style>
<div class="a">Normal</div>
<div class="b">Nudged down 14px and right 30px; its old space is still reserved</div>
<div class="a">Normal (does not move up)</div>

On its own, relative is rarely useful for nudging. Its main job is to become the reference point for absolutely positioned children.

absolute

position: absolute takes the element out of the flow: neighbours behave as if it were not there. It is placed using top, right, bottom and left relative to its nearest positioned ancestor (an ancestor whose position is anything but static).

html
<style>
  .card { position: relative; width: 220px; padding: 16px; background: #f1f5f9; border-radius: 8px; }
  .badge { position: absolute; top: -8px; right: -8px; background: #ef4444; color: white; font-size: 12px; padding: 4px 8px; border-radius: 999px; }
</style>
<div class="card">
  <span class="badge">New</span>
  A card with a corner badge.
</div>

The pairing is the pattern to memorise: relative parent, absolute child. Without position: relative on the card, the badge would jump to a corner of the page.

Absolute elements are great for badges, close buttons, tooltips and overlays. A full-cover overlay is position: absolute; inset: 0; inside a relative parent.

fixed

position: fixed positions relative to the viewport, so it stays put while the page scrolls:

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

Typical uses are floating buttons and cookie banners. Because fixed boxes cover content, keep them small, make sure they never hide important text, and check them on phones, where the visible area changes as the browser toolbar moves.

sticky

position: sticky is a hybrid: the element scrolls normally until it reaches an offset you give, then it sticks like fixed, but only within its parent.

html
<style>
  .scroller { height: 160px; overflow: auto; border: 2px solid #94a3b8; }
  .scroller h3 { position: sticky; top: 0; margin: 0; padding: 6px; background: #fde68a; }
  .scroller p { margin: 0; padding: 12px; }
</style>
<div class="scroller">
  <h3>Section A</h3>
  <p>Text</p><p>Text</p><p>Text</p>
  <h3>Section B</h3>
  <p>Text</p><p>Text</p><p>Text</p>
</div>

Scroll inside the box: each heading sticks at the top until the next one pushes it away. Sticky needs an offset (such as top: 0) to work, and does not work if an ancestor between it and the scrolling container has overflow: hidden.

z-index and stacking

When positioned boxes overlap, z-index decides which is on top: a higher number wins. It only applies to positioned elements (and to flex and grid items).

css
.overlay { position: absolute; inset: 0; z-index: 10; }

Stacking is organised into stacking contexts. Certain properties (position with z-index, opacity below 1, transform, filter and more) create a new context, and z-indexes only compete within their own context. This is why z-index: 9999 sometimes cannot beat a neighbour: its parent's context ranks below. Keep z-index values small and organised (for example, 1 for dropdowns, 10 for sticky headers, 100 for modals), and when stuck, look for the parent that created the context.

Common mistakes

  • Forgetting position: relative on the parent, so absolute children fly off to the page edge.
  • Using absolute positioning for page layout, which breaks when content or screens change.
  • Huge z-index numbers instead of understanding stacking.
  • Fixed elements covering content or the browser's own buttons on a phone.
  • Sticky that doesn't stick because no offset is set or an ancestor clips overflow.

Exercise

Create a card with an image area and a "Sale" label in its top-left corner, and make the card's title bar stick to the top of the scrolling area.

Show one possible answer
html
<style>
  .card { position: relative; width: 240px; background: #e2e8f0; border-radius: 8px; overflow: hidden; }
  .img { height: 120px; background: #93c5fd; }
  .label { position: absolute; top: 8px; left: 8px; background: #dc2626; color: white; padding: 2px 10px; border-radius: 4px; font-size: 13px; }
</style>
<div class="card">
  <span class="label">Sale</span>
  <div class="img"></div>
  <p style="padding:12px;margin:0">Product name</p>
</div>

The card is relative, so the label is positioned against it.

Key points

  • Normal flow (position static) lays elements out one after another; the other values move boxes from that flow.
  • relative keeps the element's space but lets you nudge it, and it also becomes the reference for absolute children.
  • absolute removes the element from flow and places it relative to its nearest positioned ancestor.
  • fixed sticks to the viewport; sticky behaves normally until it reaches a scroll threshold, then sticks.
  • z-index controls stacking, but only on positioned elements and within the same stacking context.

Check yourself

1. An absolutely positioned element is placed relative to what?
2. Which value keeps a header visible at the top while scrolling, but only after you reach it?
3. You give an element z-index: 999 and nothing changes. What is a likely reason?
4. What happens to the space of an element set to position: absolute?

Free tools for this lesson