Understanding CSS Positioning: 10 Code-Along Examples

Learn CSS positioning by running it. Ten copy-and-run examples covering static, relative, absolute, fixed and sticky, offset properties, z-index stacking, and the relative-parent pattern behind badges, tooltips, and modals.

The positioning article covers the five values of the position property, the offset properties that move elements around, and z-index for deciding what sits on top. This workbook makes each behaviour visible. You will see the difference between relative and absolute, pin an element to the viewport, make a header stick on scroll, layer elements with z-index, and build the real components these tools exist for: badges, tooltips, modals, and a floating button. The single most important idea, a relative parent holding an absolute child, appears in Example 5 and then everywhere after.

1. Static: the default flow

Every element starts as position: static, meaning it sits in normal document flow and ignores the offset properties entirely. This is the baseline everything else departs from.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="box">Box 1</div>
<div class="box target">Box 2 (static, offsets ignored)</div>
<div class="box">Box 3</div>
</body>
</html>
body { font-family: sans-serif; margin: 1rem; }
.box { background: #e8eef5; padding: 1rem; margin-bottom: 0.5rem; border-radius: 6px; }
.target {
position: static; /* the default */
top: 40px; /* ignored: static elements don't respond to offsets */
left: 40px; /* also ignored */
}

The three boxes stack normally, and Box 2 does not budge despite the top and left values. That is the lesson: offset properties do nothing until you change position to something other than static. Delete the position line entirely and nothing changes, which is why it is called the default.

2. Relative: nudge, but keep the space

position: relative shifts an element from where it would normally sit, measured by the offset properties, but crucially it leaves its original space reserved. Nothing else moves to fill the gap.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="box">Box 1</div>
<div class="box shifted">Box 2 (relative, nudged)</div>
<div class="box">Box 3</div>
</body>
</html>
body { font-family: sans-serif; margin: 1rem; }
.box { background: #e8eef5; padding: 1rem; margin-bottom: 0.5rem; border-radius: 6px; }
.shifted {
position: relative;
top: 20px; /* move 20px down from its normal spot */
left: 30px; /* and 30px right */
background: #1f4e78;
color: white;
}

Box 2 moves down and right, but watch the gap: Box 3 stays exactly where it was, because the original space is still held. Relative movement is a visual nudge only, which makes it perfect for small adjustments, and, as Example 5 shows, for becoming an anchor for absolute children.

3. Absolute: removed from flow

position: absolute pulls an element out of normal flow entirely, so other elements collapse as if it were never there, and positions it against the page (for now; Example 5 changes that). The offsets measure from the page edges.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="box">Box 1</div>
<div class="box floated">Box 2 (absolute)</div>
<div class="box">Box 3</div>
</body>
</html>
body { font-family: sans-serif; margin: 1rem; }
.box { background: #e8eef5; padding: 1rem; margin-bottom: 0.5rem; border-radius: 6px; }
.floated {
position: absolute;
top: 20px;
right: 20px; /* 20px from the top-right of the page */
background: #1f4e78;
color: white;
}

Compare this with Example 2: Box 3 has now moved up to sit directly under Box 1, because the absolute Box 2 no longer occupies any space in the flow. The element floats to the page’s top-right corner, disconnected from its siblings. That removal from flow is the defining trait of absolute positioning.

4. Fixed: pinned to the viewport

position: fixed pins an element to the viewport, so it stays in place while the rest of the page scrolls underneath it. This is how nav bars, cookie banners, and floating buttons stay visible.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="banner">Fixed banner: I stay put while you scroll.</div>
<div class="content">
<p>Scroll down...</p>
<p style="height: 1200px;">...the banner stays pinned to the top.</p>
</div>
</body>
</html>
body { font-family: sans-serif; margin: 0; }
.banner {
position: fixed;
top: 0;
left: 0;
right: 0; /* stretch across the top of the viewport */
background: #1f4e78;
color: white;
padding: 1rem;
}
.content { padding: 4rem 1rem 1rem; } /* top padding so content clears the banner */

Scroll the page and the banner never moves; it is anchored to the screen, not the document. One practical detail: because fixed elements leave normal flow, they can cover the content beneath them, which is why the content has top padding to push it clear of the banner.

5. The key pattern: relative parent, absolute child

This is the most important idea in CSS positioning. An absolute element positions itself against its nearest positioned ancestor, so by making a parent position: relative, you turn it into the anchor its absolute children measure from.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="card">
A product card.
<span class="tag">NEW</span>
</div>
</body>
</html>
body { font-family: sans-serif; margin: 2rem; }
.card {
position: relative; /* becomes the anchor for absolute children */
background: #e8eef5;
padding: 2rem;
max-width: 280px;
border-radius: 8px;
}
.tag {
position: absolute;
top: -10px;
right: -10px; /* measured from the CARD's corner, not the page */
background: #b71c1c;
color: white;
padding: 0.25rem 0.6rem;
border-radius: 999px;
font-size: 0.75rem;
}

The tag sits at the card’s top-right corner, not the page’s, because the card is position: relative. Remove that one line from .cardand the tag jumps to the corner of the whole page, since it would then measure from the next positioned ancestor up, which is the page. This relative-parent-plus-absolute-child pairing is behind badges, dropdowns, tooltips, and almost every “put this precisely inside that” layout.

6. Sticky: relative until it hits an edge

position: sticky is a hybrid: the element behaves relatively while it scrolls normally, then sticks like a fixed element once it reaches the offset you specify. It is the modern way to make section headers that follow you.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<section>
<h2 class="heading">Section A (sticky heading)</h2>
<p style="height: 600px;">Scroll down and watch the heading stick to the top.</p>
</section>
<section>
<h2 class="heading">Section B (sticky heading)</h2>
<p style="height: 600px;">Then this heading takes over.</p>
</section>
</body>
</html>
body { font-family: sans-serif; margin: 0; }
.heading {
position: sticky;
top: 0; /* stick once its top edge reaches the top of the viewport */
background: #1f4e78;
color: white;
padding: 1rem;
margin: 0;
}

As you scroll, heading A rides along until it reaches the top, then stays there until heading B pushes it out of the way. Sticky needs a top (or other offset) value to know when to stick, and it only works while its parent section is still on screen. It gives you fixed-like behaviour scoped to a section, with no JavaScript.

7. Z-index: deciding what sits on top

When positioned elements overlap, z-index decides the stacking order: higher numbers sit in front. It only affects elements that are positioned, so setting z-index on a static element does nothing.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="chip red">z-index 1</div>
<div class="chip blue">z-index 3</div>
<div class="chip green">z-index 2</div>
</body>
</html>
body { font-family: sans-serif; margin: 3rem; }
.chip {
position: absolute;
width: 120px;
height: 120px;
color: white;
padding: 0.5rem;
border-radius: 8px;
}
.red { background: #b71c1c; top: 40px; left: 40px; z-index: 1; }
.blue { background: #1f4e78; top: 70px; left: 90px; z-index: 3; }
.green { background: #1f7a4d; top: 100px; left: 140px; z-index: 2; }

Although green comes last in the HTML, blue sits on top because its z-index of 3 is the highest. Change blue’s z-index to 0 and it drops beneath the others. Without any z-index, later elements in the source naturally stack on top, so z-index is how you override that default order deliberately.

8. A tooltip on hover

Tooltips combine the key pattern with a hover state. The tooltip is an absolute child of a relative trigger, hidden until you hover, then revealed above the element.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<span class="trigger">
Hover me
<span class="tooltip">Helpful hint that appears above.</span>
</span>
</body>
</html>
body { font-family: sans-serif; margin: 4rem; }
.trigger {
position: relative; /* anchor for the tooltip */
background: #e8eef5;
padding: 0.5rem 0.9rem;
border-radius: 6px;
cursor: help;
}
.tooltip {
position: absolute;
bottom: 130%; /* sit above the trigger */
left: 0;
width: 180px;
background: #11243a;
color: white;
padding: 0.5rem;
border-radius: 6px;
font-size: 0.85rem;
display: none; /* hidden by default */
}
.trigger:hover .tooltip {
display: block; /* revealed on hover */
}

Hover the trigger and the tooltip appears above it, positioned by bottom: 130% relative to the trigger. This is the same relative-parent, absolute-child pattern from Example 5, now combined with a :hover rule to control visibility. Every tooltip you have ever seen is built roughly this way.

9. A modal with a backdrop

A modal uses fixed positioning to cover the whole viewport with a dimmed backdrop, then centres a dialog on top of it, with z-index making sure it sits above everything else.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<p>Page content behind the modal.</p>
<div class="overlay">
<div class="modal">
<h3>Confirm action</h3>
<p>This dialog sits above a dimmed backdrop.</p>
<button>OK</button>
</div>
</div>
</body>
</html>
body { font-family: sans-serif; margin: 1rem; }
.overlay {
position: fixed;
top: 0; right: 0; bottom: 0; left: 0; /* cover the whole viewport */
background: rgba(17, 36, 58, 0.6); /* the dim backdrop */
z-index: 100; /* above page content */
display: flex;
align-items: center;
justify-content: center; /* centre the dialog */
}
.modal {
background: white;
padding: 2rem;
border-radius: 10px;
max-width: 320px;
}

The overlay pins to all four viewport edges at once, top: 0; right: 0; bottom: 0; left: 0, which is the standard way to make a fixed element fill the screen. Its high z-index lifts it above the page, and flexbox centres the white dialog. This is the anatomy of nearly every modal on the web.

10. Putting it together: a page with everything

The finale combines the values: a fixed header, a sticky sub-nav that parks below it, a card with an absolute badge, and a fixed floating action button, all layered with z-index.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header class="site-header">Fixed header</header>
<nav class="subnav">Sticky sub-nav</nav>
<main>
<div class="card">
Featured product
<span class="badge">SALE</span>
</div>
<p style="height: 1000px;">Scroll to see the sub-nav stick under the header.</p>
</main>
<button class="fab">+</button>
</body>
</html>
body { font-family: sans-serif; margin: 0; }
.site-header {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 20; /* highest: always on top */
background: #11243a; color: white; padding: 1rem;
}
.subnav {
position: sticky;
top: 56px; /* stick just below the fixed header's height */
z-index: 10;
background: #1f4e78; color: white; padding: 0.75rem 1rem;
}
main { padding: 5rem 1rem 1rem; } /* clear the fixed header */
.card {
position: relative; /* anchor for the badge */
background: #e8eef5; padding: 2rem; max-width: 280px; border-radius: 8px;
}
.badge {
position: absolute;
top: -10px; right: -10px; /* the card's corner */
background: #b71c1c; color: white;
padding: 0.25rem 0.6rem; border-radius: 999px; font-size: 0.75rem;
}
.fab {
position: fixed;
bottom: 1.5rem; right: 1.5rem; /* floating in the viewport corner */
z-index: 20;
width: 56px; height: 56px; border-radius: 50%;
border: 0; background: #1f4e78; color: white; font-size: 1.5rem;
}

Every value earns its place: the header is fixed so it never scrolls away, the sub-nav is sticky with top: 56px so it parks neatly under the header, the badge is absolute inside a relative card, and the action button is fixed to the corner. The z-index values keep the header above the sub-nav above the page. This is a real interface’s positioning skeleton in one file.

Work through these and you have the whole article in practice: static, relative, absolute, fixed, and sticky, the offset properties that move them, z-index for stacking, and the relative-parent-absolute-child pattern that underlies most component positioning. The mental checklist to carry: decide whether an element should stay in flow, be nudged, be removed and anchored to a parent, pin to the viewport, or stick on scroll, and reach for the matching value.

See you soon.

View Comments (2)

Leave a Reply

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading