The CSS display property: block, inline, flex, grid, none
An inline element with width 300px and height 300px measured 36 by 17. Display decides whether your sizes are obeyed at all.

display is the most consequential property in CSS, because it decides two separate things at once: how the element behaves among its siblings, and how its own children are laid out.
display: block;
display: inline;
display: inline-block;
display: flex;
display: grid;
display: none;
The value that catches people is inline, and it is worth seeing why before anything else.
inline ignores width and height
The same declarations on two elements that differ only in display:
#inl { display: inline; width: 300px; height: 300px; margin: 20px; }
#ib { display: inline-block; width: 300px; height: 60px; }
Measured:
| Element | Rendered size |
|---|---|
display: inline |
36.44 × 17 |
display: inline-block |
300 × 60 |
The inline element ignored both dimensions entirely and sized itself to its text. The width: 300px is in the stylesheet, it appears in the computed styles, and it does nothing.
That is not a bug. An inline element is part of a line of text, and a line of text cannot have a box of arbitrary height inserted into it without breaking the line. So inline elements are sized by their content, always.
Vertical margins and padding behave the same way: they are accepted and do not push anything. Horizontal margins do work, which makes the behaviour feel inconsistent until you know the rule.
If you set a width or a height and nothing happens, check whether the element is inline. <span>, <a>, <em>, <strong> and <label> all are by default.
The outer and inner halves
Modern CSS defines display as two values, and thinking of it that way makes the whole property make sense:
display: block flow; /* the old `block` */
display: inline flow; /* the old `inline` */
display: block flex; /* the old `flex` */
display: inline flex; /* the old `inline-flex` */
The outer value says how the element behaves in its parent: does it take a full line (block) or sit in the text flow (inline)?
The inner value says how its children are laid out: normal flow, flex, or grid.
That explains a pairing people find confusing. display: flex makes the element a block on the outside, taking the full width, while laying its children out as flex items. display: inline-flex makes the same flex container sit inline among text.
You can write the two-value syntax today, and almost nobody does, because the single keywords are shorter and universally understood. It is worth knowing as a mental model rather than as syntax.
block: full width, new line
Measured, a block element with width: 300px rendered at 300 × 60, and one with no width rendered at 976px, filling its parent.
That default is worth stating plainly: a block element already fills the width available to it. Adding width: 100% does not help and can hurt, because a percentage width plus padding overflows the parent under the default box sizing. That interaction comes from the default box-sizing, where width sets the content box and padding is added on top.
Block elements stack vertically, each starting on a new line. <div>, <p>, <h1> through <h6>, <section> and <ul> are block by default.
inline-block: the middle ground
Sits in the text flow like inline, respects width, height, and all padding and margins like block.
It was the standard tool for laying out a row of boxes before flexbox existed, and it has one behaviour left over from that era worth knowing: inline-block elements are affected by whitespace in your HTML. A newline between two of them renders as a space, producing a gap of a few pixels that no CSS rule explains.
<div class="ib"></div>
<div class="ib"></div> <!-- a real gap appears between these -->
The old fixes were removing the whitespace, setting font-size: 0 on the parent, or commenting the gap out. All of them are workarounds for a layout method that has been superseded. display: flex with gap does the same job without the quirk, and that is the reason inline-block sees much less use than it used to.
Where it is still right: an element that genuinely belongs in a line of text and needs a size, such as a badge or an icon inside a sentence.
none: gone completely
Measured, display: none rendered at 0 × 0, and visibility: hidden on an identical element rendered at 100 × 100.
That is the difference, and it is the one to remember:
| Takes up space? | Read by screen readers? | |
|---|---|---|
display: none |
No | No |
visibility: hidden |
Yes | No |
opacity: 0 |
Yes | Yes |
display: none removes the element from the layout entirely. Everything after it moves up. visibility: hidden leaves the gap exactly as it was and just stops painting.
opacity: 0 is the one to be careful with. The element is invisible, takes up space, is still focusable, still clickable, and is still announced by assistive technology. An "invisible" button that keyboard users can still tab into is a real accessibility bug, and this is where it comes from.
There is also hidden as an HTML attribute, which applies display: none and is the tidier choice when the state belongs to the content rather than to a stylesheet.
flex and grid, in one paragraph each
display: flex lays children out along a single axis, sizing them to their content and distributing the leftover space. It is the tool for a row of buttons, a navigation bar, or centring one thing inside another.
display: grid lays children out in rows and columns you define in advance, sizing the tracks rather than the items. It is the tool for a page layout or a card grid where things should line up across both axes.
The full comparison, with measurements, is in flexbox vs grid. Both are covered properly in the complete flexbox guide.
The values you will meet less often
display: contents removes the element's own box while keeping its children in the layout. It is how you let a wrapper's children participate directly in a parent grid. It has had accessibility bugs in the past where the element's semantics were dropped along with its box, so check current support before using it on anything with a role.
display: list-item is what <li> uses. It is block plus a marker, and setting it on another element gives that element a bullet.
display: table and its relatives replicate table layout without table markup. Largely obsolete now, and still occasionally the shortest route to a specific vertical-alignment behaviour.
display: flow-root creates a block that contains its floats. It is the modern, side-effect-free replacement for the overflow: hidden clearfix hack, which matters because that hack breaks sticky positioning in descendants.
The HTML defaults worth knowing
Every element has a default display from the browser's own stylesheet. Knowing the common ones saves a lot of guessing:
| Default | Elements |
|---|---|
block |
div, p, h1 to h6, section, article, header, footer, main, nav, form, ul, ol, figure |
inline |
span, a, em, strong, code, label, abbr, small |
inline-block |
button, input, select, textarea, img (effectively) |
list-item |
li |
table, table-row, table-cell |
table, tr, td |
none |
head, title, script, style, template |
The third row explains a common confusion. Form controls and images already respect width and height without you changing anything, which is why they feel different from <span> even though they also sit in a line of text.
The last row is worth noticing too: display: none is not a special mechanism, it is the same property, applied by the browser's default stylesheet to elements that are not meant to render.
Responsive display switching
Changing display at a breakpoint is one of the most common responsive techniques, and it has a cost worth stating.
.sidebar { display: none; }
@media (min-width: 48em) {
.sidebar { display: block; }
}
That hides the sidebar on small screens for everyone, including screen-reader users, because display: none removes the element from the accessibility tree as well as from the layout. Sometimes that is exactly right: a decorative panel has no value on a phone.
When the content should still be available but not visible, use a visually-hidden utility instead:
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
That keeps the element in the accessibility tree while removing it from view, and it is the correct tool for things like a heading that gives a section a name for assistive technology without appearing on screen.
The decision each time: is this content irrelevant on small screens, or just not worth showing? The first is display: none, the second is a visually-hidden class or a different layout.
Changing display changes what your other properties mean
This is the part worth internalising. Many CSS properties only apply to certain display types, and switching display silently turns them on or off:
widthandheightdo nothing oninline.justify-contentdoes nothing unless the element is a flex or grid container.grid-template-columnsdoes nothing unless it is a grid.floatis ignored on flex and grid items.vertical-alignapplies to inline and table-cell elements, and is ignored on flex items, which is why it never fixes flexbox alignment.
So "this property is not working" is very often "this element is not the display type that property applies to". Checking display first resolves a surprising share of CSS confusion.
display: contents, and the caution attached
It removes an element's own box while keeping its children in the layout:
.wrapper { display: contents; }
The wrapper stops generating a box, so its children become direct participants in the grandparent's layout. That is the fix for a component that adds a <div> you did not want between a grid container and its items.
The caution is real. Browsers historically dropped the element's semantics along with its box, so display: contents on a <ul> or a <button> removed it from the accessibility tree, turning a list into a set of unrelated items. Most engines have fixed this for list and table elements, and the safe position remains: use it on generic wrappers such as <div> and <span>, not on anything that carries meaning.
Where it shines is exactly that case. A layout wrapper has no semantics to lose, and removing its box is the whole point.
Three common mistakes
Setting a width on an inline element. Nothing happens, and the computed style still reports the width. Change it to inline-block or block.
Using opacity: 0 to hide something interactive. It stays focusable and stays in the accessibility tree. Use display: none, or the hidden attribute, unless you are animating it.
Reaching for display: none to hide something responsively without considering what it costs. The content is removed for everyone, including screen-reader users. If it should be available but not visible, a visually-hidden utility class is the correct tool.
Quick reference
display: block; /* full width, new line */
display: inline; /* in the text flow, ignores width/height */
display: inline-block; /* in the flow, respects width/height */
display: flex; /* one-dimensional layout of children */
display: grid; /* two-dimensional layout of children */
display: none; /* removed, takes no space */
display: flow-root; /* contains floats, no side effects */
display: contents; /* box removed, children promoted */
visibility: hidden; /* invisible, keeps its space */
opacity: 0; /* invisible, keeps space AND stays focusable */
Want to change one keyword and watch a layout rearrange? Start with the CSS track.
More from the blog

Flexbox vs Grid: how to choose in ten seconds
Flexbox vs grid: flex sizes items to their content, grid sizes the tracks. Measured, that gave 32px and 229px against three equal 193px columns.
Read more
CSS specificity: why your style is not applying
Specificity is three numbers compared left to right, not one score. Here are the contests that decide it, measured in a browser.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.