Normal flow
With no CSS at all, boxes are laid out in normal flow: block-level boxes stack down the page, each filling the width available; inline boxes sit in a line, wrapping when they run out of room.
Everything else in CSS — flex, grid, floats, positioning — is a way of stepping out of normal flow. Knowing what it does on its own is what makes the rest predictable.
block and inline
block | inline | |
|---|---|---|
| Width | Fills the container | As wide as its content |
| Starts a new line | Yes | No |
width / height | Respected | Ignored |
| Vertical margin and padding | Respected | Padding draws but does not push; margin is ignored |
| Horizontal margin and padding | Respected | Respected |
| Examples | div, p, h1, section, li | span, a, strong, em, code |
Setting height or margin-top on an <a> or a <span> does nothing. Its vertical padding will paint a background over the lines above and below without moving them. Change display first.
inline-block
inline-block sits in a line like an inline box, but keeps a block's box model: width, height and vertical margins all work. It was the standard answer for horizontal layout before flexbox, and it is still right for something that belongs inside a sentence but needs a size — a badge, a small button.
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 999px;
background: #eee;
}
One quirk: inline-block elements are separated by whatever whitespace is between them in the HTML, because that whitespace is a real space character. That gap is why people once wrote their markup with no line breaks between tags. Flexbox has gap and no such problem.
none and hidden
display: noneremoves the element from layout and from assistive technology. Nothing is reserved.- The
hiddenattribute in HTML does the same thing, and expresses the intent in the markup where it belongs. visibility: hiddenkeeps the space and hides the content. It is still skipped by screen readers.opacity: 0keeps the space and the element is still focusable and clickable — a classic accessibility trap.- To hide visually but keep it for a screen reader, use the visually-hidden pattern, not any of the above.
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
The display property is really two values
display: flex is shorthand for display: block flex — the outer value says how the box behaves in its parent, the inner value says how its children are laid out.
display: block flow; /* = block */
display: inline flow; /* = inline */
display: block flex; /* = flex */
display: inline flex; /* = inline-flex */
display: block grid; /* = grid */
display: block flow-root;/* = flow-root */
That is why inline-flex exists: a flex container that itself sits in a line of text. The children do not know the difference.
flow-root
display: flow-root makes an element a block formatting context: it contains its children's floats, and margins inside it stop collapsing through it. It exists to do that one job cleanly, replacing the old overflow: hidden trick which also clipped things you wanted to see.
contents
display: contents removes the element's own box while keeping its children, which lets them become direct items of a grandparent grid or flex container. Use it carefully: in several browsers it removed the element from the accessibility tree as well, and although that is fixed for most elements, it is still not a tool to reach for on anything semantic.
.passthrough {
display: contents;
}
Changing display changes the game
A <li> is display: list-item. Set display: flex on it and the marker disappears. A <table> set to display: block loses every table behaviour. display does not just describe the element — it selects the layout algorithm, and the rest of your CSS is interpreted by that algorithm.
This is also why float, vertical-align and margin: auto behave differently, or not at all, depending on the display value of the parent.