Skip to content

CSS Positioning — relative, absolute, fixed, and sticky

Every element on a page has a position value. Most elements are never given one explicitly — they use the default. But understanding positioning is what lets you build a header that stays on screen while the user scrolls, a badge floating over a card image, or an overlay that sits on top of everything else.

Every element starts with position: static. This means the element follows normal flow — it sits where the browser places it based on document order and the box model rules from earlier lessons. You will never write position: static yourself; it is the starting baseline, not something you set intentionally.

The other four position values all change two things: how the element relates to the flow around it, and how the offset properties (top, right, bottom, left) work.

position: relative does two things:

  1. Offsets the element from its normal position using top, right, bottom, or left
  2. Creates a positioning context for any absolutely positioned descendants inside it
.callout {
position: relative;
top: 10px; /* shifts 10px downward from where it would normally sit */
}

The key detail: position: relative does not remove the element from flow. Its original space is still reserved — nearby elements behave as if it never moved. The offset is purely visual.

In practice, you will often set position: relative with no offset values at all — purely to establish a positioning context for an absolutely positioned child, like a badge over a card image.

position: absolute removes the element from normal flow entirely. The browser collapses the surrounding elements into the space the element previously occupied.

An absolutely positioned element positions itself relative to its nearest ancestor with a non-static position (any value other than static). If no such ancestor exists, it positions itself relative to the initial containing block — effectively the viewport.

/* The card creates the positioning context */
.tour-card {
position: relative;
}
/* The badge positions itself inside the card's corners */
.tour-badge {
position: absolute;
top: 1rem;
right: 1rem;
background-color: #2c4a1e;
color: #ffffff;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 700;
}

The badge now floats over the top-right corner of the card, regardless of the card’s height or content.

The analogy: position: absolute is like a sticky note — you pull it out of the document stack and place it exactly where you want. The stack closes around the gap as if the note was never there.

position: fixed also removes the element from normal flow, but positions it relative to the viewport — the browser window itself. The element stays in place as the user scrolls.

.floating-cta {
position: fixed;
bottom: 2rem;
right: 2rem;
}

Use position: fixed for elements that should always be visible regardless of scroll position: floating action buttons, persistent cookie banners, or chat widgets.

position: sticky is the hybrid. It behaves like position: relative while the element is within its scroll container, then transitions to fixed-like behavior once the element reaches a specified threshold.

The threshold is set with top, right, bottom, or left. Without that value, sticky does nothing — a very common beginner mistake.

.site-header {
position: sticky;
top: 0; /* stick when the header reaches 0px from the viewport top */
}

The header starts in its natural document position. As the user scrolls and the header reaches the top of the viewport, it locks there and the rest of the page scrolls underneath it.

When positioned elements overlap, the browser needs to decide which one appears in front. That is what z-index controls.

z-index only works on positioned elements — elements with any position value other than static. It has no effect on elements that are position: static.

Higher values appear in front of lower values:

.site-header {
position: sticky;
top: 0;
z-index: 100;
}

Without z-index: 100, the sticky header may appear behind scrolling content — cards, images, and positioned elements all contribute to stacking. A high z-index ensures the header stays on top of everything.

You do not need to use specific numbers — use values that give you room to add more layers. A common convention: navigation at 100, modals at 1000, tooltips at 200. Gaps between values matter so you can insert layers later.

The sticky header: all three properties together

Section titled “The sticky header: all three properties together”

The STO site header needs all three positioning properties working together:

.site-header {
position: sticky; /* activates sticky behavior */
top: 0; /* locks at the very top of the viewport */
z-index: 100; /* stays above all scrolling content */
background-color: #2c4a1e;
}

Remove any one of these and the behavior breaks:

  • Without position: sticky — the header scrolls away with the page
  • Without top: 0 — sticky activates at the wrong point, or not at all
  • Without z-index: 100 — the header disappears behind scrolling cards and images

Apply positioning to the STO site:

  1. Add sticky header styles to style.css:
.site-header {
position: sticky;
top: 0;
z-index: 100;
background-color: #2c4a1e;
}

Open the page in a browser and scroll — the header should remain fixed at the top of the viewport while the content scrolls beneath it.

  1. Add position: relative to .tour-card. Then add a badge element to one tour card in tours.html:
<div class="tour-card">
<span class="tour-badge">Popular</span>
<!-- rest of card content -->
</div>

Add the badge styles to style.css:

.tour-badge {
position: absolute;
top: 1rem;
right: 1rem;
background-color: #2c4a1e;
color: #ffffff;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 700;
}
  1. In DevTools, inspect .site-header. In the Styles panel, toggle position: sticky off — the header should scroll with the page. Toggle it back on.

  2. Inspect .tour-badge. In the Computed tab, look for the “Containing block” information — it should show the .tour-card as the containing block, confirming that the card’s position: relative is acting as the positioning context.

  • position: static is the default — elements follow normal flow.
  • position: relative offsets an element visually from its natural position; the original space is preserved. Also establishes a positioning context for absolutely positioned children.
  • position: absolute removes an element from flow and positions it relative to the nearest non-static ancestor. Without a positioned ancestor, it positions relative to the viewport.
  • position: fixed removes from flow and positions relative to the viewport, staying in place during scroll.
  • position: sticky acts like relative until a scroll threshold, then locks like fixed. Requires a top, right, bottom, or left value to activate.
  • z-index controls stacking order and only works on positioned (non-static) elements. The sticky header requires z-index to stay above scrolling content.

Module 04 covers color, typography, and the visual properties that give the STO site its brand identity.