Skip to main content

📐 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: none with visibility: hidden and 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 my width work?" is usually a display question. "Why is my badge in the wrong corner?" is usually a position question. 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:

graph TD A[display] --> B[block] A --> C[inline] A --> D[inline-block] A --> E[none] A --> F[flex] A --> G[grid] B --> B1[Full width, new line, size respected] C --> C1[Flows with text, size ignored] D --> D1[Flows with text, size respected] E --> E1[Removed entirely, no space] F --> F1[1-D flexbox layout] G --> G1[2-D grid layout]

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

Comparing block, inline, and inline-block flow Block elements stack full-width on their own lines; inline elements sit within a line of text and ignore width and height; inline-block elements flow inline but honour set dimensions. block stacks, full width new line each inline flows in a line; width/height ignored inline-block flows inline, but honours size display:none would leave this whole row empty — the element takes no space at all.
Figure 1 — The three everyday display modes. Block claims a full line, inline rides inside text, and inline-block combines "flows like text" with "sizes like a box."

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-block or flex for a horizontal row.
  • Show/hide interactions toggle display: none from JavaScript.
  • Responsive layouts often swap display values at breakpoints — block on mobile, flex on 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:

graph TD A[position] --> B[static] A --> C[relative] A --> D[absolute] A --> E[fixed] A --> F[sticky] B --> B1[Default; offsets ignored] C --> C1[Shift from own spot; keeps space] D --> D1[Out of flow; vs positioned ancestor] E --> E1[Out of flow; vs viewport] F --> F1[Relative until threshold, then fixed]

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 block
  • right — distance from the right edge
  • bottom — distance from the bottom edge
  • left — 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-index only affects positioned elements (not static).
  • 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: 9999 can 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:

  1. Create a .product-card with position: relative, a border, and some padding.
  2. Inside it, add a .sale-badge pinned to the top-right corner with position: absolute.
  3. Add a "Quick view" button that, when clicked, would show a .modal.
  4. Style the modal so a dimmed .modal-backdrop covers the viewport with position: fixed; inset: 0; and the .modal-content sits 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 relative to establish a context, absolute for children pinned inside it.
  • Keep z-index values 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 fixed elements can obscure content or misbehave with mobile keyboards.
  • Don't expect width/height to affect a plain inline element — switch it to inline-block or block first.
  • Don't use display: none when 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: none removes an element and its space; visibility: hidden only 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

🚀 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.