📐 Display and Positioning Properties
Every layout you will ever build rests on two CSS properties: display, which decides how an element behaves in the flow, and position, which decides where it sits. Learn these two well and the rest of CSS layout stops feeling like guesswork.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain the difference between block, inline, and inline-block and choose the right one
- Contrast
display: nonewithvisibility: hiddenand know when to reach for each - Use the five position values — static, relative, absolute, fixed, sticky — with offset properties
- Control overlap with z-index and understand what a stacking context is
- Combine these tools into common patterns: centering, overlays, badges, and modals
Estimated Time: 35–45 minutes • Difficulty: Beginner–Intermediate
Hands-on: Build a product card with an absolutely positioned "Sale" badge and a perfectly centered modal.
In This Lesson
Layout as Arranging a Room
Think of building a web layout like arranging furniture in a room. For every element you place, you make a few decisions: what type of furniture it is (does a sofa span the whole wall, or is it a small stool that tucks beside other things?), where exactly it sits, how much space it claims, and how it relates to everything around it.
CSS gives you two master controls for those decisions. The display property answers "what kind of box is this and how does it flow?" The position property answers "where does this box sit, and does it stay in the flow or float above it?" Almost everything else in layout builds on top of these two.
💡 The big idea: Most layout confusion comes from mixing up these two questions. "Why won't mywidthwork?" is usually adisplayquestion. "Why is my badge in the wrong corner?" is usually apositionquestion. Keep them separate in your head and debugging gets far easier.
The Display Property
The display property is arguably the single most important property for layout. It determines how an element behaves in the normal document flow and how it relates to its neighbours. Here is the family of values you will meet most often:
We will study flex and grid in depth in later modules — they are the modern workhorses of layout. In this lesson we focus on the four foundational values every developer uses daily: block, inline, inline-block, and none.
Core Display Values
block
Block elements start on a new line, stretch to fill the available width by default, and fully respect width, height, margin, and padding. Examples: <div>, <p>, <h1>, <section>.
inline
Inline elements flow inside a line of text and only take up as much width as their content needs. Crucially, they ignore width and height, and vertical margins do not push other content. Examples: <span>, <a>, <strong>, <em>.
inline-block
The hybrid. It flows inside text like an inline element but honours width, height, and margins on all four sides like a block. This made it the classic choice for horizontal nav bars and simple grids before flexbox existed.
none
Removes the element completely: it renders nothing and reserves no space. This is different from visibility: hidden, which hides the element but keeps its empty footprint in the layout.
📖 display:none vs visibility:hidden
display: none — gone from the layout and the accessibility tree. Screen readers skip it. No space reserved.
visibility: hidden — invisible but its box still occupies space, and it may still be announced by assistive tech depending on context.
Changing an element's display
Display is not fixed by the tag — you reassign it freely. A common example is turning navigation links into padded, clickable inline-block buttons:
/* Turn plain links into a horizontal button row */
nav.horizontal-menu a {
display: inline-block;
padding: 10px 15px;
margin: 0 5px;
background-color: #4CAF50;
color: white;
text-decoration: none;
border-radius: 4px;
}
/* Force an inline <span> to behave like a sized block */
span.badge-block {
display: block;
width: 120px;
height: 40px;
}
✅ Real-world uses
- Navigation menus use
inline-blockorflexfor a horizontal row. - Show/hide interactions toggle
display: nonefrom JavaScript. - Responsive layouts often swap display values at breakpoints —
blockon mobile,flexon desktop.
The Position Property
Where display controls how an element flows, position controls how it is placed relative to the page or its ancestors. There are five values:
static
The default. The element sits in normal flow and ignores top/right/bottom/left and z-index. You rarely write it explicitly.
relative
The element stays in the flow — its original space is preserved — but you can nudge it visually with the offset properties. Just as important, position: relative turns the element into a positioning context for any absolutely positioned children. This "relative parent, absolute child" pairing is the most important pattern in this whole lesson.
absolute
The element is removed from the flow entirely (it reserves no space) and is positioned relative to its nearest positioned ancestor — the closest parent whose position is anything other than static. If none exists, it falls back to the page's initial containing block.
fixed
Also removed from flow, but positioned relative to the viewport, so it stays put as the page scrolls. Perfect for sticky headers, chat widgets, cookie bars, and back-to-top buttons.
sticky
A hybrid: the element behaves like relative until you scroll past a threshold you set (for example top: 0), at which point it "sticks" like fixed within its scroll container. Section headers in a long list are the classic use.
/* The essential relative-parent / absolute-child pairing */
.card {
position: relative; /* becomes the reference frame */
}
.card .badge {
position: absolute; /* placed against the card, not the page */
top: -10px;
right: -10px;
}
/* A header that stays visible while scrolling */
.site-header {
position: sticky;
top: 0;
z-index: 10;
}
⚠️ The #1 positioning gotcha
position: absolute is measured against the nearest positioned ancestor — not just any parent. If your absolutely positioned element jumps to the wrong place, the fix is almost always to add position: relative to the container you meant to anchor it to.
Offsets & z-index
Once an element is positioned (anything other than static), four offset properties place it precisely:
top— distance from the top edge of the containing blockright— distance from the right edgebottom— distance from the bottom edgeleft— distance from the left edge
/* Pin to the bottom-right corner */
.corner {
position: absolute;
bottom: 10px;
right: 10px;
}
/* Perfectly centre an element of unknown size */
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Full-cover overlay (all four offsets = 0) */
.overlay {
position: absolute;
inset: 0; /* modern shorthand for top/right/bottom/left: 0 */
background: rgba(0, 0, 0, 0.5);
}
💡 Modern shorthand
The inset property is a one-line shorthand for all four offsets. inset: 0 means "stretch to every edge," and inset: 10px 20px sets vertical then horizontal — handy for overlays and full-bleed backgrounds.
Stacking with z-index
When positioned elements overlap, z-index decides who sits on top. Higher values render in front. A few rules trip people up:
z-indexonly affects positioned elements (notstatic).- Values can be positive or negative; the default is
auto(treated as 0). - Elements only compete within the same stacking context. A child with
z-index: 9999can never escape above a sibling of its parent if the parent forms its own context.
/* A modal that sits above everything, with a dimmed backdrop */
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
z-index: 1001; /* one above the backdrop */
}
⚠️ Stacking contexts create surprises
Properties like opacity below 1, transform, filter, and will-change each create a new stacking context. Inside one, your z-index values are sealed off from the rest of the page. If a huge z-index "isn't working," an ancestor has almost certainly opened its own context.
Common Layout Patterns
Display and position combine into a handful of patterns you will reach for constantly.
Centering an element
/* Horizontal centering of a block with a known width */
.center-block {
width: 50%;
margin-inline: auto; /* left & right auto */
}
/* Dead-centre, both axes, unknown size — the flexbox way (preferred) */
.center-flex {
display: flex;
justify-content: center; /* horizontal */
align-items: center; /* vertical */
min-height: 200px;
}
/* Dead-centre with the absolute + transform trick (pre-flexbox) */
.center-absolute {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
Image with a caption overlay
.image-container {
position: relative;
display: inline-block;
}
.overlay-text {
position: absolute;
inset: auto 0 0 0; /* pin to the bottom edge */
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px;
}
Card with a corner badge
.card {
position: relative;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card .badge {
position: absolute;
top: -10px;
right: -10px;
width: 30px;
height: 30px;
border-radius: 50%;
background: #e11d48;
color: white;
display: flex; /* centre the number inside */
align-items: center;
justify-content: center;
}
✅ Notice the recurring recipe
Overlay caption, corner badge, dropdown menu, tooltip — they are all the same move: a position: relative wrapper containing a position: absolute child. Learn it once and you have learned a dozen UI components.
Hands-on Exercise
🏋️ Build a Product Card with a Sale Badge and a Modal
Objective: Practise the relative-parent / absolute-child pattern and a centered fixed overlay.
Instructions:
- Create a
.product-cardwithposition: relative, a border, and some padding. - Inside it, add a
.sale-badgepinned to the top-right corner withposition: absolute. - Add a "Quick view" button that, when clicked, would show a
.modal. - Style the modal so a dimmed
.modal-backdropcovers the viewport withposition: fixed; inset: 0;and the.modal-contentsits dead-centre.
💡 Hint
Remember that the badge must be a child of the relatively positioned card, or it will anchor to the page instead. For the modal, give the backdrop a z-index and the content one higher. Centre the content with top: 50%; left: 50%; transform: translate(-50%, -50%);.
✅ Sample solution
<div class="product-card">
<span class="sale-badge">-20%</span>
<h3>Trail Running Shoe</h3>
<p>$96.00</p>
</div>
<div class="modal-backdrop">
<div class="modal-content">
<h3>Trail Running Shoe</h3>
<p>Lightweight, grippy, waterproof.</p>
</div>
</div>
.product-card {
position: relative;
width: 220px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.sale-badge {
position: absolute;
top: -10px;
right: -10px;
background: #e11d48;
color: #fff;
padding: 4px 8px;
border-radius: 999px;
font-size: 0.8rem;
}
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
padding: 24px;
border-radius: 8px;
z-index: 1001;
}
Best Practices
✅ Do
- Rely on normal flow first; reach for positioning only when the flow can't express what you want.
- Use
relativeto establish a context,absolutefor children pinned inside it. - Keep
z-indexvalues low and organised (e.g. 10 for headers, 100 for dropdowns, 1000 for modals). - Prefer flexbox or grid for real layout; use inline-block only for small in-text elements.
⚠️ Don't
- Don't sprinkle random four- and five-digit z-index values to "win" — fix the stacking context instead.
- Don't forget that
fixedelements can obscure content or misbehave with mobile keyboards. - Don't expect
width/heightto affect a plaininlineelement — switch it toinline-blockorblockfirst. - Don't use
display: nonewhen you only mean to hide something visually but keep it for screen readers — use a visually-hidden utility instead.
Summary & Quiz
🎉 Key Takeaways
- display decides how a box flows: block (stacks), inline (rides in text), inline-block (both), none (gone).
display: noneremoves an element and its space;visibility: hiddenonly hides it.- position decides where a box sits: static, relative, absolute, fixed, sticky.
- The relative-parent / absolute-child pairing powers badges, overlays, dropdowns, and tooltips.
- z-index orders overlapping positioned elements — but only within their stacking context.
🎯 Quick Quiz
Question 1: You set width: 120px on a <span> but nothing changes. Why?
Question 2: An absolutely positioned badge is anchoring to the whole page instead of its card. What is the usual fix?
Question 3: Which value keeps an element in normal flow until you scroll past a threshold, then pins it in place?
📚 Further Reading
- MDN — The display property
- MDN — The position property
- MDN — Understanding z-index & stacking contexts
🚀 What's Next?
Next we step back in time to the layout technique that ruled the web before flexbox: floats. You'll see how float and clear work, why they cause containers to collapse, and how the modern display: flow-root tames them.
🎉 Well done!
You now own the two properties every layout is built on. Everything else is variations on this theme.