025: Display and Normal Flow
Learning outcomes
By the end of this lesson, you should be able to explain normal flow, tell the difference between the outer behavior of block, inline, and inline-block boxes, use display: none intentionally, recognize formatting contexts, and solve straightforward page structure without Flexbox, Grid, floats, or positioning.
Prerequisites and retrieval
Open the fluid portfolio from 024. Recall the layers of the box model and why a block's size should usually come from its content. Before changing any CSS, predict whether two <p> elements will share a line or appear on separate lines, and whether an <a> will break a sentence. Those familiar defaults are consequences of display type and normal flow.
Terminology
- Normal flow: The default layout mode in which block boxes stack in the block direction and inline content wraps into line boxes. — Source: CSS Display 3: Flow layout
- Block box: A box that participates in block layout and normally begins on a new line. — Source: CSS Display 3: Block-level boxes
- Inline box: A box that flows inside line boxes alongside text and other inline content. — Source: MDN: Visual formatting model
- Inline-block: A box with inline outer display and block formatting inside: it flows alongside inline content while accepting block-like sizing. — Source: CSS Display 3
- Outer display type: The way a box participates in its parent formatting context, such as block or inline. — Source: CSS Display 3: Outer display
- Inner display type: The rules an element uses to lay out its children, such as flow, flex, or grid. — Source: CSS Display 3: Inner display
- Formatting context: The collection of layout rules used to arrange a box's children. — Source: MDN: Visual formatting model
- Anonymous box: A generated box that wraps text or markup for which there is no explicit element box. — Source: CSS Display 3: Anonymous boxes
- Block Formatting Context (BFC): "A block formatting context is a part of a visual CSS rendering that contains floats and prevents margin collapsing through its boundaries." — Source: MDN: Visual formatting model & CSS Display Level 3: BFC
- Line box: "A rectangular area that contains inline-level content on one line." — Source: MDN: Visual formatting model
Mental model: document order is the baseline layout
Normal flow is still layout; it is not the absence of layout. Block boxes stack in the block direction. Inline content is placed into line boxes and wraps when the available inline space is exhausted. The browser's user-agent stylesheet gives common elements useful defaults: headings, paragraphs, sections, and lists are block-level, while links and emphasis are inline.
The display property controls box generation and participation in layout. It does not change what an element means in HTML. Setting display: block on a <span> does not turn that span into a paragraph. Choose semantic HTML first, then use CSS to choose the presentation.
Modern display is best understood as having outer and inner behavior. display: flex creates a block-level box whose children use flex layout; inline-flex creates an inline-level box whose children use flex layout. For now, the useful focus is block, inline, and inline-block, while the document remains in its natural order.
Beginner example: observe defaults
<main class="site-shell">
<p class="eyebrow">Frontend portfolio</p>
<h1>Asha Rao</h1>
<p>I build <a href="#projects">clear project pages</a> with semantic HTML.</p>
<section id="projects">
<h2>Selected work</h2>
<article class="project-card">
<h3>Library finder</h3>
<p>Search opening hours and directions.</p>
<a class="button-link" href="#">Read case study</a>
</article>
</section>
</main>
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
body { margin: 0; font-family: system-ui, sans-serif; line-height: 1.6; }
.site-shell { max-inline-size: 70rem; margin-inline: auto; padding: 1rem; }
.project-card { max-inline-size: 36rem; padding: 1rem; border: 1px solid rgb(203 213 225); }
.button-link {
display: inline-block;
padding: 0.6rem 0.9rem;
border: 2px solid currentColor;
border-radius: 0.4rem;
font-weight: 700;
}
Even without the CSS, the source order produces a usable document. The heading and paragraphs stack, and the link remains part of its sentence. .button-link stays alongside neighboring inline content, but its inline-block box can accept predictable padding and dimensions. That does not mean every text link should become a block; the surrounding context should decide.
Try the behavior directly. Set h1 { display: inline; } and observe the following text join its line. Then set the sentence link to display: block and watch the link interrupt the prose. Restore both declarations afterward. Seeing the line change is more useful here than memorizing a list of defaults.
Intermediate example: normal-flow page sections
A centered page can be built without introducing a layout system:
.site-header,
.site-main,
.site-footer {
padding-inline: max(1rem, calc((100% - 70rem) / 2));
}
.site-header { padding-block: 3rem; background: rgb(15 23 42); color: white; }
.site-main { padding-block: 2rem; }
.site-footer { padding-block: 2rem; border-block-start: 1px solid rgb(203 213 225); }
.project-card + .project-card { margin-block-start: 1rem; }
.tag {
display: inline-block;
margin-inline-end: 0.35rem;
margin-block-end: 0.35rem;
padding: 0.2em 0.55em;
border-radius: 999px;
background: rgb(219 234 254);
}
The sections stay in DOM order, cards stack without extra layout machinery, and each card can grow as its content grows. The max() expression keeps at least 1rem of edge padding and adds more when the viewport has room around a 70rem content area. It is a stable option, not a requirement; a nested .site-shell is often easier to read and works just as well.
Hiding content
The browser's user-agent stylesheet already gives the HTML hidden attribute its expected presentation, so authors normally do not need to add a rule for it. Use hidden when content is genuinely inactive, and remove the attribute when that content becomes available. The ordinary hidden state removes the element and its descendants from layout and normally from the accessibility tree.
Avoid a blanket [hidden] { display: none; } rule. It also overrides hidden="until-found", a state that starts hidden but can be revealed by find-in-page or fragment navigation. Neither hidden state is suitable for text that should remain continuously available to screen readers. visibility: hidden keeps the layout space while hiding the content. opacity: 0 only makes the painting transparent, so focusable and clickable content can remain present. It is not a safe general hiding technique.
If a component really needs to override the default hidden presentation, scope the rule to that component and to the ordinary state, for example .tabs [hidden]:not([hidden="until-found"]). Prefer the user-agent behavior unless the component has a tested reason to differ. Keeping the override local preserves the browser's reveal behavior and makes the exception easy to review.
Optional advanced example: a new block formatting context
display: flow-root establishes a block formatting context:
.component {
display: flow-root;
padding: 1rem;
background: white;
}
That context can contain legacy floated content and stop certain margin interactions from crossing its boundary, without requiring a meaningless wrapper element. Floats still make sense when text should wrap around media, not as a modern substitute for page layout. Ordinary cards do not need flow-root; the important skill is recognizing it when you encounter it in maintained CSS.
Mistakes, debugging, and DevTools
- Changing display to compensate for incorrect semantics: start with the correct HTML element.
- Expecting inline text to accept vertical
width/heightbehavior: inline boxes can fragment across lines. - Making navigation links
inline-blockwithout testing wrapping: long labels can overflow. - Using
display: noneto visually hide an accessible label: assistive technology generally cannot access it. - Treating
opacity: 0as hidden: invisible interactive controls may still be reachable. - Using Flexbox just to stack ordinary prose: normal flow already handles that relationship reliably.
- Using floats for cards or site columns: reserve Flexbox and Grid for relationships that actually need them.
When debugging, inspect the final display value in DevTools' Computed pane. Add outlines to neighboring elements and resize the viewport so line wrapping becomes visible. Use the accessibility tree panel to check whether hidden content is exposed. Toggling the browser's user-agent rules can also show which defaults are responsible for block and inline behavior.
Accessibility and performance
Source order is the baseline for reading order, keyboard order, and normal-flow order. Keep that order logical instead of using visual CSS to compensate for confusing HTML. If a control becomes inline-block, it still needs a sufficient target size, a visible focus indicator, and enough room for labels to wrap. Never hide content while it has focus.
Normal flow is resilient and generally efficient. Fewer layout overrides mean less CSS to reason about and fewer surprising reflows. When performance matters, look first at media and fonts rather than treating the fact that a semantic section is block-level as a problem.
Deep dive: outer and inner display behavior
The display property describes how a box participates in its parent's layout and, for many values, how that box lays out its own children.
.block-box { display: block; }
.inline-box { display: inline; }
.inline-block-box { display: inline-block; }
.flex-box { display: flex; }
.grid-box { display: grid; }
A block box normally begins on a new line and, when its width is auto, expands in the inline direction. An inline box participates inside line boxes and does not accept all sizing behavior in the same way. An inline-block box is inline from its parent's perspective but acts as a block container for its children.
Deep dive: display: none, visibility, and opacity
These properties can all make something disappear from view, but they do not produce the same layout or interaction state:
.a { display: none; }
.b { visibility: hidden; }
.c { opacity: 0; }
display: noneremoves the box from layout and generally from the accessibility tree.visibility: hiddenkeeps layout space but hides the box and its descendants visually/interaction-wise unless overridden in certain cases.opacity: 0makes the box transparent but it still participates in layout and can remain interactive/focusable.
That is why opacity: 0 is not a safe general-purpose way to hide a control.
Deep dive: flow-root and formatting contexts
.article {
display: flow-root;
}
flow-root creates a new block formatting context without changing the element into Flexbox or Grid. The new context can contain floats and isolate certain margin interactions.
Float example
<article class="story">
<img class="story__thumb" src="author.jpg" alt="">
<p>Long article text wraps beside the image...</p>
</article>
.story {
display: flow-root;
}
.story__thumb {
float: inline-start;
inline-size: 8rem;
margin-inline-end: 1rem;
margin-block-end: 0.5rem;
}
Floats were frequently pressed into service for whole-page layout. Their more appropriate modern use is narrower: let editorial text genuinely wrap beside media, such as an image in an article.
Worked example: inline versus inline-block
<p>
Read the
<a class="tag" href="/css">CSS course</a>
today.
</p>
.tag {
display: inline-block;
padding: 0.25rem 0.5rem;
border: 1px solid currentColor;
border-radius: 999px;
}
Here inline-block gives the link a self-contained box that can receive padding while allowing it to flow alongside the sentence. If the tag should break across lines like ordinary text, a regular inline box may be the better choice.
Worked example: hiding a disclosure panel correctly
HTML:
<button aria-expanded="false" aria-controls="details">
Details
</button>
<div id="details" hidden>
<p>Additional information.</p>
</div>
The hidden attribute expresses the disclosure panel's semantic hidden state. Once behavior is added, JavaScript can toggle that attribute. This keeps what assistive technology reports aligned with what sighted users see, instead of creating a CSS-only state that tells the two audiences different stories.
CSS can style the visible state:
[hidden] {
display: none !important;
}
Browsers already implement this behavior, so a custom rule is usually unnecessary.
Deep dive: normal flow remains valuable inside modern components
A page may use Grid for the relationship between cards:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: 1rem;
}
That does not require every .card to use another layout mode. Its headings and paragraphs can continue in normal block flow. Choose Flexbox or Grid for relationships that need coordination, not as a default for every wrapper.
Display debugging questions
When an element's behavior seems wrong, ask these questions in order:
- What formatting context does its parent establish?
- Is this element block-level, inline-level, flex item, or grid item?
- Was it removed from layout with
display: none? - Is it transparent but still interactive?
- Did
float,position, ordisplay: contentschange box generation? - Are you changing layout mode only to fix spacing that normal flow already handles?
Begin with normal flow. Introduce a stronger layout system only when the relationship between elements actually requires one.
Tiered exercises
Checkpoint: solve with the baseline first
Take a project article and remove its layout declarations, leaving type, color, padding, and borders in place. Read the result from top to bottom. If the design only calls for a heading, a description, and an action in sequence, normal flow is already the right solution. Add a layout mode when the baseline cannot express the required relationship clearly.
Next, put three tags after a paragraph and narrow the viewport. Inline content wraps at text boundaries; inline-block tags wrap as intact boxes whenever each box fits. A tag wider than its container still needs breakable content or a size constraint. Changing display cannot make an unbreakable URL break.
Use one noninteractive paragraph to compare the hiding methods. display: none removes its space and accessibility exposure. visibility: hidden keeps the space. opacity: 0 keeps both the space and possible interaction. Restore the paragraph after inspecting each state. In production, choose the method from the intended semantic state, not its visual effect alone, and test focusable descendants before shipping.
Inline formatting has another practical consequence: text may split into several line fragments, so a background or decoration can appear on every fragment. Padding at the start and end of lines can look unlike one rectangular control. When the design calls for one intact badge or button-like rectangle, inline-block is appropriate. Give long labels room to wrap in the surrounding design rather than forcing overflow. For ordinary prose links, inline behavior is still the most natural and readable choice.
Foundation: Classify each portfolio element as block or inline by default. Temporarily change one element's display and explain the result you observe.
Core: Build stacked project cards with normal flow and tags that wrap as inline-block boxes. Style a case-study link as a clearly padded control with visible focus.
Stretch: Use hidden for inactive supplementary content. Inspect the layout and accessibility tree before and after removing the attribute.
<article class="project-card">
<h3>Library finder</h3>
<p><span class="tag">HTML</span><span class="tag">Accessibility</span></p>
<a class="button-link" href="#">Read case study</a>
<p hidden>Draft testing notes.</p>
</article>
.project-card { padding: 1rem; border: 1px solid rgb(203 213 225); }
.project-card + .project-card { margin-block-start: 1rem; }
.tag { display: inline-block; margin: 0 0.35rem 0.35rem 0; padding: 0.2em 0.55em; background: rgb(219 234 254); }
.button-link { display: inline-block; padding: 0.6rem 0.9rem; border: 2px solid currentColor; }
.button-link:focus-visible { outline: 3px solid rgb(234 88 12); outline-offset: 3px; }
Recap and exit questions
Normal flow is a dependable baseline: blocks stack and inline content wraps. display changes how boxes are generated and participate in layout, not the semantics of the HTML element. Use inline-block when something should flow inline but also needs block-like geometry.
- Why is normal flow already a layout system?
- What changes when a link becomes
inline-block? - Does
display: blockgive a<span>paragraph semantics? - Why is
opacity: 0unsafe for hiding interactive content? - When would
flow-rootbe useful?
