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.
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:
widthandheightare 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: flexanddisplay: gridare the modern tools for building layouts, and they get their own lessons.display: flow-rootanddisplay: contentsalso 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
positionother thanstatic(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-indexonly works on positioned elements (anything butstatic), 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
positionplus az-index, or withopacitybelow 1, or atransform), its children are stacked inside it. A child can then never rise above something outside the parent, whatever its number. If a largez-indexseems to do nothing, check the parents in DevTools. - Do not escalate to
z-index: 99999. Use a small scale (10for headers,100for 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: nowrapkeeps the text on one line.overflow: hiddenclips what does not fit.text-overflow: ellipsisdraws 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
widthorheighton an inline element (like<span>or<a>) and wondering why nothing happens. Useinline-blockorblock. - Using
position: absolutewithout 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
toporlefton astaticelement. Nothing happens until you set aposition. - Using
z-indexon a static element, or fighting a parent’s stacking context with huge numbers. - Using
display: nonewhen you wanted to keep the space, or the other way round. - Hiding overflow and cutting off content (
overflow: hiddenon a box whose height is fixed), especially with larger text sizes. - Using
position: fixedbars that cover content on phones, without leaving room below them.
Practice
- Click Practice in Editor. Make the 3rd tag a block, the 4th an inline-block with
width: 160px, hide the 5th withdisplay: none, and make the 6thvisibility: hidden. What is different about the space each one leaves? - Move
.child-relative20px down and 40px right. Does.child-absolutemove too? - Remove
position: relativefrom.parentand watch where.child-absolutegoes. Then put it back. - Build the badge on the card, then move it to the top-left corner and to the bottom-right.
- Change the
z-indexvalues so square 2 is on top, and then so that square 3 is on top. - Change
.scroll-boxfromoverflow: autotohidden,scrollandvisible. Describe each result. - Make the
.truncateparagraph end in..., then removewhite-space: nowrapand see what breaks.
Recap
displaycontrols 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: noneremoves an element,visibility: hiddenonly hides it.positionhas five values:static(default),relative,absolute,fixedandsticky. 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-indexstacks overlapping positioned elements, but only within the same stacking context.overflowhandles extra content:visible,hidden,scrollorauto. Addtext-overflow: ellipsisfor one-line truncation.- Next you will style lists, links and forms.