CSSIntermediate

CSS display, position, z-index and overflow Explained

Learn CSS display modes, positioning, z-index stacking, and overflow to control element layout, layering, and content that exceeds its container.

All CSS lessons

What you will learn

The box model tells you what a box is made of. This lesson is about where boxes go and how they behave next to each other. You will learn how display decides whether an element sits on its own line or flows with text, how position lets you nudge, pin and overlap elements, how z-index decides which overlapping element is on top, and how overflow handles content that does not fit. Each idea has a live example on one practice page.

The HTML we will style

<h1 class="page-title">Display and Position Lab</h1>

<!-- Part 1: display -->
<div class="tags">
  <span class="tag">Inline one</span>
  <span class="tag">Inline two</span>
  <span class="tag tag-block">Block tag</span>
  <span class="tag tag-inline-block">Inline-block tag</span>
  <span class="tag tag-hidden">display: none</span>
  <span class="tag tag-invisible">visibility: hidden</span>
</div>

<!-- Part 2: position -->
<div class="parent">
  <div class="child child-static">static (default)</div>
  <div class="child child-relative">relative</div>
  <div class="child child-absolute">absolute</div>
</div>

<!-- Part 3: a badge on a card -->
<div class="badge-card">
  <span class="badge">NEW</span>
  <p class="badge-card-text">A card with a badge in the corner.</p>
</div>

<!-- Part 4: z-index -->
<div class="stack">
  <div class="layer layer-1">1</div>
  <div class="layer layer-2">2</div>
  <div class="layer layer-3">3</div>
</div>

<!-- Part 5: overflow -->
<div class="scroll-box">
  <p>This box is only 100px tall, but the text inside is much longer. ...</p>
</div>
<p class="truncate">This long sentence is cut off with three dots when it does not fit.</p>
Selector Element Experiment
.tag all six <span>s shared look (background, padding, margin)
.tag-block, .tag-inline-block the 3rd and 4th span display: block and inline-block
.tag-hidden, .tag-invisible the 5th and 6th span display: none vs visibility: hidden
.parent the grey wrapper <div> the positioned container
.child-static, .child-relative, .child-absolute three <div>s inside .parent position values
.badge-card and .badge a card and the label in its corner the classic absolute-in-relative pattern
.stack and .layer-1/2/3 a container and three overlapping squares z-index
.scroll-box, .truncate a short box with long text, and a long one-line paragraph overflow

Part 1: display

Every element has a display type that decides how it behaves in the flow of the page. Three matter most.

block

A block element starts on a new line and fills the full width available. <div>, <p>, <h1>, <ul> and <section> are block by default. You can set width, height, margin and padding on all four sides.

inline

An inline element flows within a line of text, like a word, and is only as wide as its content. <span>, <a>, <strong> and <em> are inline by default. Their behaviour is a little unusual:

  • width and height are ignored.
  • Left and right margin and padding work.
  • Top and bottom margin has no effect, and top and bottom padding paints outside the line without pushing neighbours away.

That is why our tags overlap the lines above them when you give an inline <span> vertical padding.

inline-block

The best of both: the element flows inline like a word, but it accepts width, height and vertical spacing like a block. It is ideal for buttons, badges and tags.

.tag {
  background-color: lightblue;
  padding: 10px 16px;
  margin: 10px;
}

/* the 3rd span: now on its own line, full width */
.tag-block {
  display: block;
}

/* the 4th span: stays inline but obeys width */
.tag-inline-block {
  display: inline-block;
  width: 160px;
}

Change any element from one type to another with the display property. You can turn a <span> into a block, or a <div> into an inline element.

none vs visibility: hidden

/* the 5th span: removed from the page completely, takes no space */
.tag-hidden {
  display: none;
}

/* the 6th span: invisible, but its space is still reserved */
.tag-invisible {
  visibility: hidden;
}

display: none removes the element from the layout and hides it from screen readers as well. visibility: hidden only makes it invisible; the empty gap remains.

More display values are coming. display: flex and display: grid are the modern tools for building layouts, and they get their own lessons. display: flow-root and display: contents also exist, but you will use them rarely.

Part 2: position

Normally elements are placed one after another in the normal flow. The position property lets you change that. Five values exist:

Value Behaviour
static The default. Normal flow. The top, left, right, bottom properties do nothing.
relative Stays in the flow, but can be nudged from its normal place.
absolute Removed from the flow and placed relative to its nearest positioned ancestor.
fixed Removed from the flow and pinned to the browser window.
sticky Behaves like relative until it reaches a scroll position, then sticks.

You place positioned elements using the offset properties top, right, bottom and left.

relative: nudge from your own spot

/* the <div class="child child-relative"> */
.child-relative {
  position: relative;
  top: 10px;
  left: 30px;
}

The element moves 10px down and 30px right from where it would normally be. Its original space stays reserved, so nothing around it shifts. Positive top moves it down, positive left moves it right, as if the offsets pushed away from that edge.

relative is used for small nudges, but mostly as the anchor for absolutely positioned children.

absolute: pinned to a container

/* the grey wrapper: becomes the anchor for its absolute children */
.parent {
  position: relative;
  height: 200px;
  background-color: #eeeeee;
}

/* the <div class="child child-absolute"> */
.child-absolute {
  position: absolute;
  top: 10px;
  right: 10px;
}

An absolutely positioned element:

  • is taken out of the flow, so siblings act as if it does not exist;
  • is placed relative to its nearest ancestor that has any position other than static (its containing block);
  • if there is no such ancestor, it is placed relative to the whole page.

That is why .parent has position: relative. Without it, .child-absolute would jump to the top-right corner of the page. Try removing that line and watch it fly away.

A width is also needed in most cases, because an absolute element shrinks to fit its content.

The badge pattern

This is one of the most common uses of absolute: a label pinned to the corner of a card.

/* the card is the anchor */
.badge-card {
  position: relative;
  width: 260px;
  padding: 24px;
  border: 1px solid #dddddd;
}

/* the <span class="badge">, stuck to the top-right corner */
.badge {
  position: absolute;
  top: -10px;
  right: -10px;
  background-color: crimson;
  color: white;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
}

Negative offsets like -10px push the badge slightly outside the corner. Positioned elements can also be centred with top: 0; right: 0; bottom: 0; left: 0; margin: auto when they have a width and height.

fixed: pinned to the window

.back-to-top {
  position: fixed;
  bottom: 16px;
  right: 16px;
}

A fixed element ignores scrolling and stays in the same spot on screen. It is used for cookie banners, chat buttons and top bars. Be careful that it does not cover important content on small screens.

sticky (a preview)

sticky acts like relative until the element would scroll out of view, then it sticks at the offset you give it, for example position: sticky; top: 0;. It gets its own lesson at the end of this section.

Part 3: z-index

When positioned elements overlap, which is on top? By default, later elements in the HTML sit above earlier ones. z-index overrides that with a number. Higher numbers are closer to the viewer.

/* the container: the anchor for the layers */
.stack {
  position: relative;
  height: 150px;
}

/* all three squares */
.layer {
  position: absolute;
  width: 100px;
  height: 100px;
  color: white;
  font-size: 32px;
}

.layer-1 { top: 0;    left: 0;    background-color: tomato;    z-index: 3; }
.layer-2 { top: 30px; left: 40px; background-color: seagreen;  z-index: 2; }
.layer-3 { top: 60px; left: 80px; background-color: royalblue; z-index: 1; }

Normally square 3 (last in the HTML) would be on top. With these numbers, square 1 is on top, and square 3 at the bottom. Things to know:

  • z-index only works on positioned elements (anything but static), and on flex and grid items.
  • Negative values are allowed and push the element behind its parent’s content.
  • Stacking contexts: if a parent creates its own stacking context (for example with position plus a z-index, or with opacity below 1, or a transform), its children are stacked inside it. A child can then never rise above something outside the parent, whatever its number. If a large z-index seems to do nothing, check the parents in DevTools.
  • Do not escalate to z-index: 99999. Use a small scale (10 for headers, 100 for modals) and stick to it.

Part 4: overflow

What happens when the content is bigger than its box? That is overflow, and you control it with the overflow property.

Value Result
visible The default. The extra content spills out of the box.
hidden The extra content is clipped and hidden.
scroll Scrollbars are always shown.
auto Scrollbars appear only if needed. Usually the best choice.
/* the <div class="scroll-box"> */
.scroll-box {
  width: 260px;
  height: 100px;
  overflow: auto;
  border: 2px solid gray;
  padding: 8px;
}

The text is longer than 100px, so a vertical scrollbar appears. Change auto to hidden and the extra text simply disappears. Use overflow-x and overflow-y to control each direction on its own.

Truncating text with an ellipsis

A single line of text that is too long can end with .... Three properties work together:

/* the <p class="truncate"> */
.truncate {
  width: 220px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
  • white-space: nowrap keeps the text on one line.
  • overflow: hidden clips what does not fit.
  • text-overflow: ellipsis draws the ... where it is clipped.

Putting it together

.tag {
  background-color: lightblue;
  padding: 10px 16px;
  margin: 10px;
}

.tag-block          { display: block; }
.tag-inline-block   { display: inline-block; width: 160px; }
.tag-hidden         { display: none; }
.tag-invisible      { visibility: hidden; }

.parent {
  position: relative;
  height: 200px;
  background-color: #eeeeee;
}

.child {
  width: 160px;
  padding: 10px;
  background-color: gold;
}

.child-relative {
  position: relative;
  top: 10px;
  left: 30px;
}

.child-absolute {
  position: absolute;
  top: 10px;
  right: 10px;
}

.badge-card {
  position: relative;
  width: 260px;
  margin: 30px 0;
  padding: 24px;
  border: 1px solid #dddddd;
}

.badge {
  position: absolute;
  top: -10px;
  right: -10px;
  padding: 4px 10px;
  border-radius: 999px;
  background-color: crimson;
  color: white;
  font-size: 12px;
}

.stack { position: relative; height: 150px; }
.layer { position: absolute; width: 100px; height: 100px; color: white; }
.layer-1 { top: 0;    left: 0;    background-color: tomato;    z-index: 3; }
.layer-2 { top: 30px; left: 40px; background-color: seagreen;  z-index: 2; }
.layer-3 { top: 60px; left: 80px; background-color: royalblue; z-index: 1; }

.scroll-box {
  width: 260px;
  height: 100px;
  overflow: auto;
  border: 2px solid gray;
  padding: 8px;
}

.truncate {
  width: 220px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

Common mistakes

  • Setting width or height on an inline element (like <span> or <a>) and wondering why nothing happens. Use inline-block or block.
  • Using position: absolute without a positioned parent, so the element flies to a corner of the page.
  • Forgetting that absolute elements leave the flow, so their parent collapses around them. Give the parent a height.
  • Trying top or left on a static element. Nothing happens until you set a position.
  • Using z-index on a static element, or fighting a parent’s stacking context with huge numbers.
  • Using display: none when you wanted to keep the space, or the other way round.
  • Hiding overflow and cutting off content (overflow: hidden on a box whose height is fixed), especially with larger text sizes.
  • Using position: fixed bars that cover content on phones, without leaving room below them.

Practice

  1. Click Practice in Editor. Make the 3rd tag a block, the 4th an inline-block with width: 160px, hide the 5th with display: none, and make the 6th visibility: hidden. What is different about the space each one leaves?
  2. Move .child-relative 20px down and 40px right. Does .child-absolute move too?
  3. Remove position: relative from .parent and watch where .child-absolute goes. Then put it back.
  4. Build the badge on the card, then move it to the top-left corner and to the bottom-right.
  5. Change the z-index values so square 2 is on top, and then so that square 3 is on top.
  6. Change .scroll-box from overflow: auto to hidden, scroll and visible. Describe each result.
  7. Make the .truncate paragraph end in ..., then remove white-space: nowrap and see what breaks.

Recap

  • display controls the type of box: block (own line, full width), inline (flows in text, ignores width and height) and inline-block (flows, but takes width and height). display: none removes an element, visibility: hidden only hides it.
  • position has five values: static (default), relative, absolute, fixed and sticky. Offsets (top, right, bottom, left) only work on positioned elements.
  • An absolute element is placed against its nearest positioned ancestor, so give the container position: relative.
  • z-index stacks overlapping positioned elements, but only within the same stacking context.
  • overflow handles extra content: visible, hidden, scroll or auto. Add text-overflow: ellipsis for one-line truncation.
  • Next you will style lists, links and forms.