—CSS Series · Post 06: Display & Positioning
Display & Positioning
Where Things Actually Go
Half of layout confusion comes from not understanding how elements flow by default. The other half comes from not knowing when to break out of that flow — and how.
💬 "Why Won't This Just Sit Next to That?"
You have two divs. You want them side by side. You push, you pull, you add margins everywhere — and they still stack vertically like they're ignoring you. Or you set position: absolute on something and it flies to a completely unexpected corner of the screen.
These are display and positioning problems, and they're caused by CSS having a very specific default behavior that most people don't realize exists until it bites them.
🧠 The Default: Normal Flow
By default, HTML elements have a flow. Block elements stack vertically, one per line, filling the available width. Inline elements sit in a line and wrap like text. This is called normal flow.
Once you understand that flow exists and how it works, you can work with it — and you'll know exactly which tools to use when you need to break out of it.
🧱 The display Property
block — Full Width, New Line
Block elements take up the full width available and force a new line before and after themselves. <div>, <p>, <h1>–<h6>, <section> are block by default.
inline — Flows with Text
Inline elements sit in the text flow. They don't start on a new line, and their width shrinks to fit their content. <span>, <a>, <strong>, <em> are inline by default. Crucially: you can't set width or height on inline elements — they ignore those properties.
inline-block — Best of Both
Inline-block sits in the text flow like inline, but respects width, height, padding, and margin like a block. This used to be the primary way to lay out navigation items and buttons before Flexbox became mainstream.
none — Hides the Element
display: none removes the element from the page entirely — it takes up no space and is invisible. This is different from visibility: hidden, which hides the element but preserves its space in the layout.
/* Override default display behavior */
.nav-item { display: inline-block; } /* sit side by side */
.pill { display: inline-block; } /* badge/tag */
.hidden { display: none; } /* gone — no space taken */
.invisible { visibility: hidden; } /* invisible — space preserved */
/* Common pattern: show/hide on mobile */
.mobile-menu { display: none; }
.mobile-menu:target { display: block; }🧱 The position Property
The position property controls how an element is placed in or out of normal flow. It has five values, and understanding the difference between them will solve most of the "why is this in the wrong place" problems you'll encounter.
static — The Default
Every element is position: static by default. This means it sits in normal flow. top, right, bottom, left have no effect on static elements — you have to change position first.
relative — Move from Its Own Spot
A relatively positioned element moves relative to where it would normally be. The original space it occupied is preserved — the element visually shifts, but doesn't push other elements around.
.nudged {
position: relative;
top: 8px; /* moves 8px DOWN from its normal position */
left: 12px; /* moves 12px RIGHT from its normal position */
}
/* Most importantly: position: relative makes this element
a POSITIONING CONTEXT for any absolute children inside it */absolute — Pinned to Its Positioned Parent
An absolutely positioned element is removed from normal flow entirely. It gets positioned relative to its nearest ancestor that has position: relative (or absolute, fixed, or sticky). If no positioned ancestor exists, it uses the <body>. This is why things fly to unexpected corners — the parent wasn't set up correctly.
/* Parent must have position: relative to "catch" the child */
.card {
position: relative; /* ← this creates the positioning context */
}
/* Badge sits at top-right of .card, not of the whole page */
.badge {
position: absolute;
top: 12px;
right: 12px;
}
/* Absolute elements are removed from flow —
they don't push other elements around */If your absolutely positioned element is flying to the top corner of the entire page, it's because its parent doesn't have position: relative. Always set the parent first. The child looks for the nearest positioned ancestor — if there isn't one, it climbs all the way up to the body.
fixed — Pinned to the Viewport
A fixed element is removed from flow and positioned relative to the browser viewport. It stays in the same spot even as the user scrolls. Classic uses: sticky navigation bars, floating action buttons, cookie banners.
/* Stays at the top of the viewport as you scroll */
.navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 100; /* stays on top of everything */
}
/* Fixed removes the element from flow — add padding-top
to the body equal to navbar height to prevent content overlap */
body { padding-top: 60px; }sticky — Scrolls Then Sticks
Sticky is a hybrid: the element scrolls normally until it hits a defined offset from the viewport edge, then it sticks in place. The most common use is section headers that stick while their section is in view.
/* Scrolls normally, then sticks when it hits top: 0 */
.section-header {
position: sticky;
top: 0;
background: #0a0a0f; /* needs a background or content shows through */
z-index: 10;
}
/* sticky only works within its scroll container —
the parent must have overflow: scroll or auto,
or be the page itself */z-index — Controlling Stack Order
When positioned elements overlap, z-index controls which one sits on top. Higher number = closer to the viewer. Only works on elements that have a position other than static.
.modal { position: fixed; z-index: 1000; } /* on top */
.overlay { position: fixed; z-index: 900; } /* below modal */
.navbar { position: fixed; z-index: 100; } /* below overlay */
.tooltip { position: absolute; z-index: 50; } /* above card */
.card { position: relative; z-index: 1; } /* base layer */
/* z-index doesn't work on position: static elements */Quick Reference
| Value | In flow? | Positioned relative to | Classic use case |
|---|---|---|---|
| static | ✓ Yes | — | Default. Everything starts here. |
| relative | ✓ Yes | Its own normal position | Minor nudges; creating context for absolute children |
| absolute | ✗ No | Nearest positioned ancestor | Badges, tooltips, dropdowns, overlays |
| fixed | ✗ No | Viewport | Sticky navbars, floating buttons, cookie banners |
| sticky | ✓ Yes | Scroll container + threshold | Section headers, in-page navigation |
Mini Build: Card with a Badge + Horizontal Nav
Two builds in one. First: a card with an absolutely positioned badge. Second: a horizontal navigation using inline-block. Both use positioning intentionally.
The card with a badge. Parent gets position: relative. Badge gets position: absolute and snaps to the corner.
<!-- HTML -->
<div class="card">
<span class="badge">New</span>
<h3 class="card-title">Post Title</h3>
<p class="card-text">Description goes here.</p>
</div>
/* CSS */
.card {
position: relative; /* ← creates the context */
background: #12121a;
border-radius: 12px;
padding: 24px;
max-width: 320px;
}
.badge {
position: absolute; /* ← breaks out of flow */
top: 14px;
right: 14px;
background: #ff69b4;
color: #fff;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
padding: 3px 10px;
border-radius: 20px;
}The horizontal navigation. Use inline-block on the nav items so they sit side by side while still accepting padding and hover states.
<!-- HTML -->
<nav class="nav">
<a class="nav-item" href="#">Home</a>
<a class="nav-item" href="#">About</a>
<a class="nav-item" href="#">Work</a>
</nav>
/* CSS */
.nav {
background: #12121a;
padding: 0 24px;
}
.nav-item {
display: inline-block; /* side by side */
padding: 16px 20px; /* clickable area */
color: #9595a8;
text-decoration: none;
font-size: 0.9rem;
transition: color 0.2s;
}
.nav-item:hover {
color: #ff69b4;
}For navigation, most modern projects now use Flexbox instead of inline-block. Flexbox gives you better alignment control and simpler code. But inline-block is worth knowing — it predates Flexbox and you'll see it everywhere in older codebases. Next post covers Flexbox in full.
💛 Layout Is Starting to Make Sense
Once you understand normal flow and how position interacts with it, a huge chunk of CSS confusion disappears. You're no longer fighting the layout system — you're working with it.
- block elements stack vertically and fill width. inline flows like text. inline-block is the hybrid.
- display: none removes an element from the page entirely — space and all
- position: relative makes an element a positioning context for its children
- position: absolute breaks out of flow — always set the parent to relative first
- position: fixed sticks to the viewport. sticky scrolls then sticks.
- z-index only works on positioned elements — not static ones
Next: Flexbox — the modern layout system that makes all the old inline-block tricks obsolete. Centering, spacing, distributing items — done properly, in one property.
I once spent two hours trying to center something using position: absolute with top: 50%; left: 50% and a margin: -50px hack. It almost worked. Almost.
Then I learned Flexbox and did it in two lines. Two. I stared at the screen for a while afterward processing the grief.
Positioning is powerful and you absolutely need to know it — badges, modals, tooltips, sticky headers — all of that needs position. But for centering content and building multi-column layouts, Flexbox is what you actually want. Learn both. Use each for its job.