CSS Positioning: Static, Relative, Absolute, Fixed, Sticky

Published Updated

CSS positioning controls how an element participates in normal flow and which box supplies the coordinates for its offsets. Think of normal flow as assigned seating: every element receives a place in order. The position value decides whether an element stays in that seat, shifts from it, leaves the row, follows the viewport, or stops moving at a scroll threshold. For the broader styling basics, start with the CSS basics. This guide is part of our CSS layout guide series.

That seating model makes the five values easier to predict. It also explains why an absolute badge can jump to a page corner and why a sticky heading can stop at the edge of its container.

If you learn by doing, jump to Build a Positioned Card Step by Step first.

What Does CSS Positioning Change?

CSS positioning controls whether an element stays in normal flow and which containing block its offsets use. The position property works with physical insets such as top and logical insets such as inset-inline-end. Choose the positioning mode before changing offsets.

static, relative, and sticky keep a place in normal flow. absolute and fixed remove the element from flow, so nearby content lays out without reserving space for it. This distinction causes most visible positioning differences.

Position Static Keeps Normal Flow

static is the default for every element. The browser places the box in its assigned seat, and physical inset properties plus z-index have no effect on an ordinary static box.

.notice {
  position: static;
  top: 2rem;
}

The notice appears exactly where normal flow placed it. The top: 2rem declaration does not move the box because static elements are not positioned elements.

Position Relative Offsets an Element

relative keeps the assigned seat but lets the element slide away from it. Other elements still behave as though the box remained in its original location.

.notice {
  position: relative;
  top: 0.5rem;
  left: 1rem;
}

A containing block is the box that supplies the coordinates the child measures its offsets from.

The notice moves down by 0.5rem and right by 1rem. Its original space remains reserved, which can leave a visible gap. A relatively positioned box also becomes a containing block for an absolutely positioned descendant.

Position Absolute Uses a Containing Block

absolute makes the box leave its assigned seat. Its containing block usually comes from the padding box of the nearest ancestor whose position is not static.

.card {
  position: relative;
}

.card-badge {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
}

The card becomes the marked row that supplies coordinates, and the badge sits 0.75rem from its top-right corner. The card content does not reserve room for the badge, so padding may be needed to prevent an overlap.

Position Fixed Follows the Viewport

CSS position: fixed removes the box from normal flow and normally positions it relative to the viewport. An ancestor with a transform, filter, or certain containment settings can establish a different containing block, so check ancestors when a fixed control scrolls unexpectedly.

.back-to-top {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
}

The button stays one rem from the viewport's right and bottom edges. Check fixed controls at narrow widths and high zoom because they can cover links, form fields, or text underneath.

Position Sticky Stops at a Threshold

sticky keeps its assigned seat until scrolling reaches a specified inset. The element then stays at that threshold within its nearest scrolling ancestor and containing block.

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

The heading scrolls normally until it touches the top threshold, then remains visible while its containing section continues past it. The background prevents scrolling content from showing through the sticky box.

Build a Positioned Card Step by Step

This sequence changes one layout rule at a time. Begin with normal flow, remove the badge from its assigned seat, define its local coordinate system, then add a sticky heading that keeps its original space.

Start with Static Flow

<section class="product-section">
  <h2 class="section-heading">New arrivals</h2>
  <article class="product-card">
    <span class="card-badge">New</span>
    <h3>Desk lamp</h3>
    <p>Warm light with three brightness levels.</p>
  </article>
</section>

With no positioning rules, the heading, badge, title, and description appear in source order. Every element has a place in flow, so nothing overlaps.

Take the Badge Out of Flow

.card-badge {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
}

The badge leaves the document flow, but the card has not supplied a positioning context yet. The browser searches farther up the ancestor chain for a containing block, so the badge may appear near the page corner.

Add a Positioned Ancestor

.product-card {
  position: relative;
  padding: 1.25rem;
  padding-top: 3rem;
}

The card now supplies the badge's coordinates, so the same offsets pin it to the card's top-right corner. Extra top padding reserves visual room even though the badge itself occupies no flow space.

Make the Section Heading Sticky

.section-heading {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #eef3f0;
}

The heading keeps its flow space and scrolls until it reaches the viewport's top edge. It then stays visible within the section. The small z-index keeps card content below the opaque heading in the same stacking context, the group of layers a browser composites together.

Common Pitfalls & Debugging

Absolute Element Uses the Wrong Ancestor

The absolute element appears at a page edge instead of inside its card. No intended ancestor establishes its containing block. Add position: relative to the specific parent that should supply the coordinates.

Z-index Does Not Change the Layer

Raising the element's z-index value produces no visible change. The element is an ordinary static box, or it sits inside a lower stacking context. Position the element and inspect ancestor stacking contexts. Flex and grid items are the main exception because their z-index can work without a non-static position.

Sticky Does Not Stick

The sticky box scrolls away or sticks inside a much smaller area. An ancestor with overflow: hidden, auto, or scroll became the nearest scrolling ancestor. Remove the unnecessary overflow rule or place the sticky element inside the intended scroll container.

Top or Left Does Nothing

An inset declaration has no visible effect. The element still uses position: static as part of normal flow. Choose relative, absolute, fixed, or sticky according to the layout behavior you need.

Positioning Best Practices

  • Keep document structure in normal flow, flexbox, or grid so content can grow without unexpected overlaps.
  • Give an absolute element a deliberate containing block and reserve visual space when the overlay could cover text.
  • Use the smallest practical z-index within a known stacking context instead of escalating to arbitrary large numbers.
  • Test fixed and sticky elements with zoom, long content, narrow screens, and keyboard focus before shipping them.

Frequently Asked Questions

Does CSS sticky positioning need an explicitly scrolling ancestor?

CSS sticky positioning does not need a specially declared scrolling ancestor. It uses the nearest ancestor with a scrolling mechanism, which may be the page. An ancestor with overflow: hidden can become that reference even without visible scrollbars. Set a non-auto inset such as top: 0 and leave enough space for the element to move.

Can flexbox or grid replace CSS positioning?

Flexbox and grid should handle most structural layout because they keep items coordinated with surrounding content. Positioning remains useful for overlays, corner badges, fixed controls, and sticky headings. These systems often work together inside the same component.

Should CSS positioning use physical or logical inset properties?

Use physical CSS insets such as top and right when the physical edge matters. Use logical insets such as inset-block-start and inset-inline-end when placement should follow the writing mode and text direction.

Does z-index affect where an absolute element is positioned?

CSS z-index controls stacking order, not an absolutely positioned element’s coordinates. Its containing block is commonly the nearest positioned ancestor, but properties such as transform can also establish one. Changing z-index alone will not move the element.

MDN Resources

Sources

  1. [1]
    position
    (developer.mozilla.org)
  2. [2]
    top
    (developer.mozilla.org)